]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
make it safer to do from daklib.dbconn import *
[dak.git] / daklib / dbconn.py
1 #!/usr/bin/python
2
3 """ DB access class
4
5 @contact: Debian FTPMaster <ftpmaster@debian.org>
6 @copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
7 @copyright: 2008-2009  Mark Hymers <mhy@debian.org>
8 @copyright: 2009  Joerg Jaspert <joerg@debian.org>
9 @copyright: 2009  Mike O'Connor <stew@debian.org>
10 @license: GNU General Public License version 2 or later
11 """
12
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
17
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
27 ################################################################################
28
29 # < mhy> I need a funny comment
30 # < sgran> two peanuts were walking down a dark street
31 # < sgran> one was a-salted
32 #  * mhy looks up the definition of "funny"
33
34 ################################################################################
35
36 import os
37 import psycopg2
38 import traceback
39
40 from sqlalchemy import create_engine, Table, MetaData, select
41 from sqlalchemy.orm import sessionmaker, mapper, relation
42
43 # Don't remove this, we re-export the exceptions to scripts which import us
44 from sqlalchemy.exc import *
45
46 from singleton import Singleton
47
48 ################################################################################
49
50 __all__ = []
51
52 ################################################################################
53
54 class Architecture(object):
55     def __init__(self, *args, **kwargs):
56         pass
57
58     def __repr__(self):
59         return '<Architecture %s>' % self.arch_string
60
61 __all__.append('Architecture')
62
63 def get_architecture(architecture, session=None):
64     """
65     Returns database id for given C{architecture}.
66
67     @type architecture: string
68     @param architecture: The name of the architecture
69
70     @type session: Session
71     @param session: Optional SQLA session object (a temporary one will be
72     generated if not supplied)
73
74     @rtype: Architecture
75     @return: Architecture object for the given arch (None if not present)
76
77     """
78     if session is None:
79         session = DBConn().session()
80     q = session.query(Architecture).filter_by(arch_string=architecture)
81     if q.count() == 0:
82         return None
83     return q.one()
84
85 __all__.append('get_architecture')
86
87 def get_architecture_suites(architecture, session=None):
88     """
89     Returns list of Suite objects for given C{architecture} name
90
91     @type source: str
92     @param source: Architecture name to search for
93
94     @type session: Session
95     @param session: Optional SQL session object (a temporary one will be
96     generated if not supplied)
97
98     @rtype: list
99     @return: list of Suite objects for the given name (may be empty)
100     """
101
102     if session is None:
103         session = DBConn().session()
104
105     q = session.query(Suite)
106     q = q.join(SuiteArchitecture)
107     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
108     return q.all()
109
110 __all__.append('get_architecture_suites')
111
112 class Archive(object):
113     def __init__(self, *args, **kwargs):
114         pass
115
116     def __repr__(self):
117         return '<Archive %s>' % self.name
118
119 __all__.append('Archive')
120
121 def get_archive(archive, session=None):
122     """
123     returns database id for given c{archive}.
124
125     @type archive: string
126     @param archive: the name of the arhive
127
128     @type session: Session
129     @param session: Optional SQLA session object (a temporary one will be
130     generated if not supplied)
131
132     @rtype: Archive
133     @return: Archive object for the given name (None if not present)
134
135     """
136     archive = archive.lower()
137     if session is None:
138         session = DBConn().session()
139     q = session.query(Archive).filter_by(archive_name=archive)
140     if q.count() == 0:
141         return None
142     return q.one()
143
144 __all__.append('get_archive')
145
146 class BinAssociation(object):
147     def __init__(self, *args, **kwargs):
148         pass
149
150     def __repr__(self):
151         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
152
153 __all__.append('BinAssociation')
154
155 class Binary(object):
156     def __init__(self, *args, **kwargs):
157         pass
158
159     def __repr__(self):
160         return '<Binary %s (%s, %s)>' % (self.package, self.version, self.architecture)
161
162 __all__.append('Binary')
163
164 def get_binary_from_id(id, session=None):
165     """
166     Returns Binary object for given C{id}
167
168     @type id: int
169     @param id: Id of the required binary
170
171     @type session: Session
172     @param session: Optional SQLA session object (a temporary one will be
173     generated if not supplied)
174
175     @rtype: Binary
176     @return: Binary object for the given binary (None if not present)
177     """
178     if session is None:
179         session = DBConn().session()
180     q = session.query(Binary).filter_by(binary_id=id)
181     if q.count() == 0:
182         return None
183     return q.one()
184
185 __all__.append('get_binary_from_id')
186
187 def get_binaries_from_name(package, session=None):
188     """
189     Returns list of Binary objects for given C{package} name
190
191     @type package: str
192     @param package: Binary package name to search for
193
194     @type session: Session
195     @param session: Optional SQL session object (a temporary one will be
196     generated if not supplied)
197
198     @rtype: list
199     @return: list of Binary objects for the given name (may be empty)
200     """
201     if session is None:
202         session = DBConn().session()
203     return session.query(Binary).filter_by(package=package).all()
204
205 __all__.append('get_binaries_from_name')
206
207 class Component(object):
208     def __init__(self, *args, **kwargs):
209         pass
210
211     def __repr__(self):
212         return '<Component %s>' % self.component_name
213
214
215 __all__.append('Component')
216
217 def get_component(component, session=None):
218     """
219     Returns database id for given C{component}.
220
221     @type component: string
222     @param component: The name of the override type
223
224     @rtype: int
225     @return: the database id for the given component
226
227     """
228     component = component.lower()
229     if session is None:
230         session = DBConn().session()
231     q = session.query(Component).filter_by(component_name=component)
232     if q.count() == 0:
233         return None
234     return q.one()
235
236 __all__.append('get_component')
237
238 class DBConfig(object):
239     def __init__(self, *args, **kwargs):
240         pass
241
242     def __repr__(self):
243         return '<DBConfig %s>' % self.name
244
245 __all__.append('DBConfig')
246
247 class ContentFilename(object):
248     def __init__(self, *args, **kwargs):
249         pass
250
251     def __repr__(self):
252         return '<ContentFilename %s>' % self.filename
253
254 __all__.append('ContentFilename')
255
256 class ContentFilepath(object):
257     def __init__(self, *args, **kwargs):
258         pass
259
260     def __repr__(self):
261         return '<ContentFilepath %s>' % self.filepath
262
263 __all__.append('ContentFilepath')
264
265 class ContentAssociation(object):
266     def __init__(self, *args, **kwargs):
267         pass
268
269     def __repr__(self):
270         return '<ContentAssociation %s>' % self.ca_id
271
272 __all__.append('ContentAssociation')
273
274 class DSCFile(object):
275     def __init__(self, *args, **kwargs):
276         pass
277
278     def __repr__(self):
279         return '<DSCFile %s>' % self.dscfile_id
280
281 __all__.append('DSCFile')
282
283 class PoolFile(object):
284     def __init__(self, *args, **kwargs):
285         pass
286
287     def __repr__(self):
288         return '<PoolFile %s>' % self.filename
289
290 __all__.append('PoolFile')
291
292 class Fingerprint(object):
293     def __init__(self, *args, **kwargs):
294         pass
295
296     def __repr__(self):
297         return '<Fingerprint %s>' % self.fingerprint
298
299 __all__.append('Fingerprint')
300
301 class Keyring(object):
302     def __init__(self, *args, **kwargs):
303         pass
304
305     def __repr__(self):
306         return '<Keyring %s>' % self.keyring_name
307
308 __all__.append('Keyring')
309
310 class Location(object):
311     def __init__(self, *args, **kwargs):
312         pass
313
314     def __repr__(self):
315         return '<Location %s (%s)>' % (self.path, self.location_id)
316
317 __all__.append('Location')
318
319 class Maintainer(object):
320     def __init__(self, *args, **kwargs):
321         pass
322
323     def __repr__(self):
324         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
325
326 __all__.append('Maintainer')
327
328 class Override(object):
329     def __init__(self, *args, **kwargs):
330         pass
331
332     def __repr__(self):
333         return '<Override %s (%s)>' % (self.package, self.suite_id)
334
335 __all__.append('Override')
336
337 class OverrideType(object):
338     def __init__(self, *args, **kwargs):
339         pass
340
341     def __repr__(self):
342         return '<OverrideType %s>' % self.overridetype
343
344 __all__.append('OverrideType')
345
346 def get_override_type(override_type, session=None):
347     """
348     Returns OverrideType object for given C{override type}.
349
350     @type override_type: string
351     @param override_type: The name of the override type
352
353     @type session: Session
354     @param session: Optional SQLA session object (a temporary one will be
355     generated if not supplied)
356
357     @rtype: int
358     @return: the database id for the given override type
359
360     """
361     if session is None:
362         session = DBConn().session()
363     q = session.query(Priority).filter_by(priority=priority)
364     if q.count() == 0:
365         return None
366     return q.one()
367
368 __all__.append('get_override_type')
369
370 class PendingContentAssociation(object):
371     def __init__(self, *args, **kwargs):
372         pass
373
374     def __repr__(self):
375         return '<PendingContentAssociation %s>' % self.pca_id
376
377 __all__.append('PendingContentAssociation')
378
379 class Priority(object):
380     def __init__(self, *args, **kwargs):
381         pass
382
383     def __repr__(self):
384         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
385
386 __all__.append('Priority')
387
388 def get_priority(priority, session=None):
389     """
390     Returns Priority object for given C{priority name}.
391
392     @type priority: string
393     @param priority: The name of the priority
394
395     @type session: Session
396     @param session: Optional SQLA session object (a temporary one will be
397     generated if not supplied)
398
399     @rtype: Priority
400     @return: Priority object for the given priority
401
402     """
403     if session is None:
404         session = DBConn().session()
405     q = session.query(Priority).filter_by(priority=priority)
406     if q.count() == 0:
407         return None
408     return q.one()
409
410 __all__.append('get_priority')
411
412 class Queue(object):
413     def __init__(self, *args, **kwargs):
414         pass
415
416     def __repr__(self):
417         return '<Queue %s>' % self.queue_name
418
419 __all__.append('Queue')
420
421 class QueueBuild(object):
422     def __init__(self, *args, **kwargs):
423         pass
424
425     def __repr__(self):
426         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
427
428 __all__.append('QueueBuild')
429
430 class Section(object):
431     def __init__(self, *args, **kwargs):
432         pass
433
434     def __repr__(self):
435         return '<Section %s>' % self.section
436
437 __all__.append('Section')
438
439 def get_section(section, session=None):
440     """
441     Returns Section object for given C{section name}.
442
443     @type section: string
444     @param section: The name of the section
445
446     @type session: Session
447     @param session: Optional SQLA session object (a temporary one will be
448     generated if not supplied)
449
450     @rtype: Section
451     @return: Section object for the given section name
452
453     """
454     if session is None:
455         session = DBConn().session()
456     q = session.query(Section).filter_by(section=section)
457     if q.count() == 0:
458         return None
459     return q.one()
460
461 __all__.append('get_section')
462
463 class Source(object):
464     def __init__(self, *args, **kwargs):
465         pass
466
467     def __repr__(self):
468         return '<Source %s (%s)>' % (self.source, self.version)
469
470 __all__.append('Source')
471
472 def get_sources_from_name(source, session=None):
473     """
474     Returns list of Source objects for given C{source} name
475
476     @type source: str
477     @param source: Source package name to search for
478
479     @type session: Session
480     @param session: Optional SQL session object (a temporary one will be
481     generated if not supplied)
482
483     @rtype: list
484     @return: list of Source objects for the given name (may be empty)
485     """
486     if session is None:
487         session = DBConn().session()
488     return session.query(Source).filter_by(source=source).all()
489
490 __all__.append('get_sources_from_name')
491
492 def get_source_in_suite(source, suite, session=None):
493     """
494     Returns list of Source objects for a combination of C{source} and C{suite}.
495
496       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
497       - B{suite} - a suite name, eg. I{unstable}
498
499     @type source: string
500     @param source: source package name
501
502     @type suite: string
503     @param suite: the suite name
504
505     @rtype: string
506     @return: the version for I{source} in I{suite}
507
508     """
509     if session is None:
510         session = DBConn().session()
511     q = session.query(SrcAssociation)
512     q = q.join('source').filter_by(source=source)
513     q = q.join('suite').filter_by(suite_name=suite)
514     if q.count() == 0:
515         return None
516     # ???: Maybe we should just return the SrcAssociation object instead
517     return q.one().source
518
519 __all__.append('get_source_in_suite')
520
521 class SrcAssociation(object):
522     def __init__(self, *args, **kwargs):
523         pass
524
525     def __repr__(self):
526         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
527
528 __all__.append('SrcAssociation')
529
530 class SrcUploader(object):
531     def __init__(self, *args, **kwargs):
532         pass
533
534     def __repr__(self):
535         return '<SrcUploader %s>' % self.uploader_id
536
537 __all__.append('SrcUploader')
538
539 class Suite(object):
540     def __init__(self, *args, **kwargs):
541         pass
542
543     def __repr__(self):
544         return '<Suite %s>' % self.suite_name
545
546 __all__.append('Suite')
547
548 def get_suite_architecture(suite, architecture, session=None):
549     """
550     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
551     doesn't exist
552
553     @type suite: str
554     @param suite: Suite name to search for
555
556     @type architecture: str
557     @param architecture: Architecture name to search for
558
559     @type session: Session
560     @param session: Optional SQL session object (a temporary one will be
561     generated if not supplied)
562
563     @rtype: SuiteArchitecture
564     @return: the SuiteArchitecture object or None
565     """
566
567     if session is None:
568         session = DBConn().session()
569
570     q = session.query(SuiteArchitecture)
571     q = q.join(Architecture).filter_by(arch_string=architecture)
572     q = q.join(Suite).filter_by(suite_name=suite)
573     if q.count() == 0:
574         return None
575     return q.one()
576
577 __all__.append('get_suite_architecture')
578
579 def get_suite(suite, session=None):
580     """
581     Returns Suite object for given C{suite name}.
582
583     @type suite: string
584     @param suite: The name of the suite
585
586     @type session: Session
587     @param session: Optional SQLA session object (a temporary one will be
588     generated if not supplied)
589
590     @rtype: Suite
591     @return: Suite object for the requested suite name (None if not presenT)
592
593     """
594     if session is None:
595         session = DBConn().session()
596     q = session.query(Suite).filter_by(suite_name=suite)
597     if q.count() == 0:
598         return None
599     return q.one()
600
601 __all__.append('get_suite')
602
603 class SuiteArchitecture(object):
604     def __init__(self, *args, **kwargs):
605         pass
606
607     def __repr__(self):
608         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
609
610 __all__.append('SuiteArchitecture')
611
612 def get_suite_architectures(suite, session=None):
613     """
614     Returns list of Architecture objects for given C{suite} name
615
616     @type source: str
617     @param source: Suite name to search for
618
619     @type session: Session
620     @param session: Optional SQL session object (a temporary one will be
621     generated if not supplied)
622
623     @rtype: list
624     @return: list of Architecture objects for the given name (may be empty)
625     """
626
627     if session is None:
628         session = DBConn().session()
629
630     q = session.query(Architecture)
631     q = q.join(SuiteArchitecture)
632     q = q.join(Suite).filter_by(suite_name=suite).order_by('arch_string')
633     return q.all()
634
635 __all__.append('get_suite_architectures')
636
637 class Uid(object):
638     def __init__(self, *args, **kwargs):
639         pass
640
641     def __repr__(self):
642         return '<Uid %s (%s)>' % (self.uid, self.name)
643
644 __all__.append('Uid')
645
646 ################################################################################
647
648 class DBConn(Singleton):
649     """
650     database module init.
651     """
652     def __init__(self, *args, **kwargs):
653         super(DBConn, self).__init__(*args, **kwargs)
654
655     def _startup(self, *args, **kwargs):
656         self.debug = False
657         if kwargs.has_key('debug'):
658             self.debug = True
659         self.__createconn()
660
661     def __setuptables(self):
662         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
663         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
664         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
665         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
666         self.tbl_component = Table('component', self.db_meta, autoload=True)
667         self.tbl_config = Table('config', self.db_meta, autoload=True)
668         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
669         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
670         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
671         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
672         self.tbl_files = Table('files', self.db_meta, autoload=True)
673         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
674         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
675         self.tbl_location = Table('location', self.db_meta, autoload=True)
676         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
677         self.tbl_override = Table('override', self.db_meta, autoload=True)
678         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
679         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
680         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
681         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
682         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
683         self.tbl_section = Table('section', self.db_meta, autoload=True)
684         self.tbl_source = Table('source', self.db_meta, autoload=True)
685         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
686         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
687         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
688         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
689         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
690
691     def __setupmappers(self):
692         mapper(Architecture, self.tbl_architecture,
693                properties = dict(arch_id = self.tbl_architecture.c.id))
694
695         mapper(Archive, self.tbl_archive,
696                properties = dict(archive_id = self.tbl_archive.c.id,
697                                  archive_name = self.tbl_archive.c.name))
698
699         mapper(BinAssociation, self.tbl_bin_associations,
700                properties = dict(ba_id = self.tbl_bin_associations.c.id,
701                                  suite_id = self.tbl_bin_associations.c.suite,
702                                  suite = relation(Suite),
703                                  binary_id = self.tbl_bin_associations.c.bin,
704                                  binary = relation(Binary)))
705
706         mapper(Binary, self.tbl_binaries,
707                properties = dict(binary_id = self.tbl_binaries.c.id,
708                                  package = self.tbl_binaries.c.package,
709                                  version = self.tbl_binaries.c.version,
710                                  maintainer_id = self.tbl_binaries.c.maintainer,
711                                  maintainer = relation(Maintainer),
712                                  source_id = self.tbl_binaries.c.source,
713                                  source = relation(Source),
714                                  arch_id = self.tbl_binaries.c.architecture,
715                                  architecture = relation(Architecture),
716                                  poolfile_id = self.tbl_binaries.c.file,
717                                  poolfile = relation(PoolFile),
718                                  binarytype = self.tbl_binaries.c.type,
719                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
720                                  fingerprint = relation(Fingerprint),
721                                  install_date = self.tbl_binaries.c.install_date,
722                                  binassociations = relation(BinAssociation,
723                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
724
725         mapper(Component, self.tbl_component,
726                properties = dict(component_id = self.tbl_component.c.id,
727                                  component_name = self.tbl_component.c.name))
728
729         mapper(DBConfig, self.tbl_config,
730                properties = dict(config_id = self.tbl_config.c.id))
731
732         mapper(ContentAssociation, self.tbl_content_associations,
733                properties = dict(ca_id = self.tbl_content_associations.c.id,
734                                  filename_id = self.tbl_content_associations.c.filename,
735                                  filename    = relation(ContentFilename),
736                                  filepath_id = self.tbl_content_associations.c.filepath,
737                                  filepath    = relation(ContentFilepath),
738                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
739                                  binary      = relation(Binary)))
740
741
742         mapper(ContentFilename, self.tbl_content_file_names,
743                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
744                                  filename = self.tbl_content_file_names.c.file))
745
746         mapper(ContentFilepath, self.tbl_content_file_paths,
747                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
748                                  filepath = self.tbl_content_file_paths.c.path))
749
750         mapper(DSCFile, self.tbl_dsc_files,
751                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
752                                  source_id = self.tbl_dsc_files.c.source,
753                                  source = relation(Source),
754                                  poolfile_id = self.tbl_dsc_files.c.file,
755                                  poolfile = relation(PoolFile)))
756
757         mapper(PoolFile, self.tbl_files,
758                properties = dict(file_id = self.tbl_files.c.id,
759                                  filesize = self.tbl_files.c.size,
760                                  location_id = self.tbl_files.c.location,
761                                  location = relation(Location)))
762
763         mapper(Fingerprint, self.tbl_fingerprint,
764                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
765                                  uid_id = self.tbl_fingerprint.c.uid,
766                                  uid = relation(Uid),
767                                  keyring_id = self.tbl_fingerprint.c.keyring,
768                                  keyring = relation(Keyring)))
769
770         mapper(Keyring, self.tbl_keyrings,
771                properties = dict(keyring_name = self.tbl_keyrings.c.name,
772                                  keyring_id = self.tbl_keyrings.c.id))
773
774         mapper(Location, self.tbl_location,
775                properties = dict(location_id = self.tbl_location.c.id,
776                                  component_id = self.tbl_location.c.component,
777                                  component = relation(Component),
778                                  archive_id = self.tbl_location.c.archive,
779                                  archive = relation(Archive),
780                                  archive_type = self.tbl_location.c.type))
781
782         mapper(Maintainer, self.tbl_maintainer,
783                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
784
785         mapper(Override, self.tbl_override,
786                properties = dict(suite_id = self.tbl_override.c.suite,
787                                  suite = relation(Suite),
788                                  component_id = self.tbl_override.c.component,
789                                  component = relation(Component),
790                                  priority_id = self.tbl_override.c.priority,
791                                  priority = relation(Priority),
792                                  section_id = self.tbl_override.c.section,
793                                  section = relation(Section),
794                                  overridetype_id = self.tbl_override.c.type,
795                                  overridetype = relation(OverrideType)))
796
797         mapper(OverrideType, self.tbl_override_type,
798                properties = dict(overridetype = self.tbl_override_type.c.type,
799                                  overridetype_id = self.tbl_override_type.c.id))
800
801         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
802                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
803                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
804                                  filepath = relation(ContentFilepath),
805                                  filename_id = self.tbl_pending_content_associations.c.filename,
806                                  filename = relation(ContentFilename)))
807
808         mapper(Priority, self.tbl_priority,
809                properties = dict(priority_id = self.tbl_priority.c.id))
810
811         mapper(Queue, self.tbl_queue,
812                properties = dict(queue_id = self.tbl_queue.c.id))
813
814         mapper(QueueBuild, self.tbl_queue_build,
815                properties = dict(suite_id = self.tbl_queue_build.c.suite,
816                                  queue_id = self.tbl_queue_build.c.queue,
817                                  queue = relation(Queue)))
818
819         mapper(Section, self.tbl_section,
820                properties = dict(section_id = self.tbl_section.c.id))
821
822         mapper(Source, self.tbl_source,
823                properties = dict(source_id = self.tbl_source.c.id,
824                                  version = self.tbl_source.c.version,
825                                  maintainer_id = self.tbl_source.c.maintainer,
826                                  maintainer = relation(Maintainer,
827                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
828                                  poolfile_id = self.tbl_source.c.file,
829                                  poolfile = relation(PoolFile),
830                                  fingerprint_id = self.tbl_source.c.sig_fpr,
831                                  fingerprint = relation(Fingerprint),
832                                  changedby_id = self.tbl_source.c.changedby,
833                                  changedby = relation(Maintainer,
834                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
835                                  srcfiles = relation(DSCFile,
836                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
837                                  srcassociations = relation(SrcAssociation,
838                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
839
840         mapper(SrcAssociation, self.tbl_src_associations,
841                properties = dict(sa_id = self.tbl_src_associations.c.id,
842                                  suite_id = self.tbl_src_associations.c.suite,
843                                  suite = relation(Suite),
844                                  source_id = self.tbl_src_associations.c.source,
845                                  source = relation(Source)))
846
847         mapper(SrcUploader, self.tbl_src_uploaders,
848                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
849                                  source_id = self.tbl_src_uploaders.c.source,
850                                  source = relation(Source,
851                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
852                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
853                                  maintainer = relation(Maintainer,
854                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
855
856         mapper(Suite, self.tbl_suite,
857                properties = dict(suite_id = self.tbl_suite.c.id))
858
859         mapper(SuiteArchitecture, self.tbl_suite_architectures,
860                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
861                                  suite = relation(Suite),
862                                  arch_id = self.tbl_suite_architectures.c.architecture,
863                                  architecture = relation(Architecture)))
864
865         mapper(Uid, self.tbl_uid,
866                properties = dict(uid_id = self.tbl_uid.c.id))
867
868     ## Connection functions
869     def __createconn(self):
870         from config import Config
871         cnf = Config()
872         if cnf["DB::Host"]:
873             # TCP/IP
874             connstr = "postgres://%s" % cnf["DB::Host"]
875             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
876                 connstr += ":%s" % cnf["DB::Port"]
877             connstr += "/%s" % cnf["DB::Name"]
878         else:
879             # Unix Socket
880             connstr = "postgres:///%s" % cnf["DB::Name"]
881             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
882                 connstr += "?port=%s" % cnf["DB::Port"]
883
884         self.db_pg   = create_engine(connstr, echo=self.debug)
885         self.db_meta = MetaData()
886         self.db_meta.bind = self.db_pg
887         self.db_smaker = sessionmaker(bind=self.db_pg,
888                                       autoflush=True,
889                                       autocommit=False)
890
891         self.__setuptables()
892         self.__setupmappers()
893
894     def session(self):
895         return self.db_smaker()
896
897     def prepare(self,name,statement):
898         if not self.prepared_statements.has_key(name):
899             pgc.execute(statement)
900             self.prepared_statements[name] = statement
901
902
903     def get_location_id(self, location, component, archive):
904         """
905         Returns database id for the location behind the given combination of
906           - B{location} - the path of the location, eg. I{/srv/ftp.debian.org/ftp/pool/}
907           - B{component} - the id of the component as returned by L{get_component_id}
908           - B{archive} - the id of the archive as returned by L{get_archive_id}
909         Results are kept in a cache during runtime to minimize database queries.
910
911         @type location: string
912         @param location: the path of the location
913
914         @type component: int
915         @param component: the id of the component
916
917         @type archive: int
918         @param archive: the id of the archive
919
920         @rtype: int
921         @return: the database id for the location
922
923         """
924
925         archive_id = self.get_archive_id(archive)
926
927         if not archive_id:
928             return None
929
930         res = None
931
932         if component:
933             component_id = self.get_component_id(component)
934             if component_id:
935                 res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND component=%(component)s AND archive=%(archive)s",
936                         {'location': location,
937                          'archive': int(archive_id),
938                          'component': component_id}, cachename='location')
939         else:
940             res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND archive=%(archive)d",
941                     {'location': location, 'archive': archive_id, 'component': ''}, cachename='location')
942
943         return res
944
945
946
947 def get_files_id (self, filename, size, md5sum, location_id):
948     """
949     Returns -1, -2 or the file_id for filename, if its C{size} and C{md5sum} match an
950     existing copy.
951
952     The database is queried using the C{filename} and C{location_id}. If a file does exist
953     at that location, the existing size and md5sum are checked against the provided
954     parameters. A size or checksum mismatch returns -2. If more than one entry is
955     found within the database, a -1 is returned, no result returns None, otherwise
956     the file id.
957
958     @type filename: string
959     @param filename: the filename of the file to check against the DB
960
961     @type size: int
962     @param size: the size of the file to check against the DB
963
964     @type md5sum: string
965     @param md5sum: the md5sum of the file to check against the DB
966
967     @type location_id: int
968     @param location_id: the id of the location as returned by L{get_location_id}
969
970     @rtype: int / None
971     @return: Various return values are possible:
972                - -2: size/checksum error
973                - -1: more than one file found in database
974                - None: no file found in database
975                - int: file id
976
977     """
978     values = {'filename' : filename,
979               'location' : location_id}
980
981     if not res:
982         query = """SELECT id, size, md5sum
983                    FROM files
984                    WHERE filename = %(filename)s AND location = %(location)s"""
985
986         cursor = self.db_con.cursor()
987         cursor.execute( query, values )
988
989         if cursor.rowcount == 0:
990             res = None
991
992         elif cursor.rowcount != 1:
993             res = -1
994
995         else:
996             row = cursor.fetchone()
997
998             if row[1] != int(size) or row[2] != md5sum:
999                 res =  -2
1000
1001             else:
1002                 res = row[0]
1003
1004     return res
1005
1006
1007 def get_or_set_contents_file_id(self, filename):
1008     """
1009     Returns database id for given filename.
1010
1011     If no matching file is found, a row is inserted.
1012
1013     @type filename: string
1014     @param filename: The filename
1015
1016     @rtype: int
1017     @return: the database id for the given component
1018     """
1019     try:
1020         values={'value': filename}
1021         query = "SELECT id FROM content_file_names WHERE file = %(value)s"
1022         if not id:
1023             c = self.db_con.cursor()
1024             c.execute( "INSERT INTO content_file_names VALUES (DEFAULT, %(value)s) RETURNING id",
1025                        values )
1026
1027             id = c.fetchone()[0]
1028
1029         return id
1030     except:
1031         traceback.print_exc()
1032         raise
1033
1034 def get_or_set_contents_path_id(self, path):
1035     """
1036     Returns database id for given path.
1037
1038     If no matching file is found, a row is inserted.
1039
1040     @type path: string
1041     @param path: The filename
1042
1043     @rtype: int
1044     @return: the database id for the given component
1045     """
1046     try:
1047         values={'value': path}
1048         query = "SELECT id FROM content_file_paths WHERE path = %(value)s"
1049         if not id:
1050             c = self.db_con.cursor()
1051             c.execute( "INSERT INTO content_file_paths VALUES (DEFAULT, %(value)s) RETURNING id",
1052                        values )
1053
1054             id = c.fetchone()[0]
1055
1056         return id
1057     except:
1058         traceback.print_exc()
1059         raise
1060
1061
1062 def insert_content_paths(self, bin_id, fullpaths):
1063     """
1064     Make sure given path is associated with given binary id
1065
1066     @type bin_id: int
1067     @param bin_id: the id of the binary
1068     @type fullpaths: list
1069     @param fullpaths: the list of paths of the file being associated with the binary
1070
1071     @return: True upon success
1072     """
1073
1074     c = self.db_con.cursor()
1075
1076     c.execute("BEGIN WORK")
1077     try:
1078
1079         for fullpath in fullpaths:
1080             (path, file) = os.path.split(fullpath)
1081
1082             # Get the necessary IDs ...
1083             file_id = self.get_or_set_contents_file_id(file)
1084             path_id = self.get_or_set_contents_path_id(path)
1085
1086             c.execute("""INSERT INTO content_associations
1087                            (binary_pkg, filepath, filename)
1088                        VALUES ( '%d', '%d', '%d')""" % (bin_id, path_id, file_id) )
1089
1090         c.execute("COMMIT")
1091         return True
1092     except:
1093         traceback.print_exc()
1094         c.execute("ROLLBACK")
1095         return False
1096
1097 def insert_pending_content_paths(self, package, fullpaths):
1098     """
1099     Make sure given paths are temporarily associated with given
1100     package
1101
1102     @type package: dict
1103     @param package: the package to associate with should have been read in from the binary control file
1104     @type fullpaths: list
1105     @param fullpaths: the list of paths of the file being associated with the binary
1106
1107     @return: True upon success
1108     """
1109
1110     c = self.db_con.cursor()
1111
1112     c.execute("BEGIN WORK")
1113     try:
1114         arch_id = self.get_architecture_id(package['Architecture'])
1115
1116         # Remove any already existing recorded files for this package
1117         c.execute("""DELETE FROM pending_content_associations
1118                      WHERE package=%(Package)s
1119                      AND version=%(Version)s
1120                      AND architecture=%(ArchID)s""", {'Package': package['Package'],
1121                                                       'Version': package['Version'],
1122                                                       'ArchID':  arch_id})
1123
1124         for fullpath in fullpaths:
1125             (path, file) = os.path.split(fullpath)
1126
1127             if path.startswith( "./" ):
1128                 path = path[2:]
1129             # Get the necessary IDs ...
1130             file_id = self.get_or_set_contents_file_id(file)
1131             path_id = self.get_or_set_contents_path_id(path)
1132
1133             c.execute("""INSERT INTO pending_content_associations
1134                            (package, version, architecture, filepath, filename)
1135                         VALUES (%%(Package)s, %%(Version)s, '%d', '%d', '%d')"""
1136                 % (arch_id, path_id, file_id), package )
1137
1138         c.execute("COMMIT")
1139         return True
1140     except:
1141         traceback.print_exc()
1142         c.execute("ROLLBACK")
1143         return False