]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
enhance binary and source find routines
[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 # Only import Config until Queue stuff is changed to store its config
47 # in the database
48 from config import Config
49 from singleton import Singleton
50 from textutils import fix_maintainer
51
52 ################################################################################
53
54 __all__ = ['IntegrityError', 'SQLAlchemyError']
55
56 ################################################################################
57
58 class Architecture(object):
59     def __init__(self, *args, **kwargs):
60         pass
61
62     def __repr__(self):
63         return '<Architecture %s>' % self.arch_string
64
65 __all__.append('Architecture')
66
67 def get_architecture(architecture, session=None):
68     """
69     Returns database id for given C{architecture}.
70
71     @type architecture: string
72     @param architecture: The name of the architecture
73
74     @type session: Session
75     @param session: Optional SQLA session object (a temporary one will be
76     generated if not supplied)
77
78     @rtype: Architecture
79     @return: Architecture object for the given arch (None if not present)
80
81     """
82     if session is None:
83         session = DBConn().session()
84     q = session.query(Architecture).filter_by(arch_string=architecture)
85     if q.count() == 0:
86         return None
87     return q.one()
88
89 __all__.append('get_architecture')
90
91 def get_architecture_suites(architecture, session=None):
92     """
93     Returns list of Suite objects for given C{architecture} name
94
95     @type source: str
96     @param source: Architecture name to search for
97
98     @type session: Session
99     @param session: Optional SQL session object (a temporary one will be
100     generated if not supplied)
101
102     @rtype: list
103     @return: list of Suite objects for the given name (may be empty)
104     """
105
106     if session is None:
107         session = DBConn().session()
108
109     q = session.query(Suite)
110     q = q.join(SuiteArchitecture)
111     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
112     return q.all()
113
114 __all__.append('get_architecture_suites')
115
116 ################################################################################
117
118 class Archive(object):
119     def __init__(self, *args, **kwargs):
120         pass
121
122     def __repr__(self):
123         return '<Archive %s>' % self.name
124
125 __all__.append('Archive')
126
127 def get_archive(archive, session=None):
128     """
129     returns database id for given c{archive}.
130
131     @type archive: string
132     @param archive: the name of the arhive
133
134     @type session: Session
135     @param session: Optional SQLA session object (a temporary one will be
136     generated if not supplied)
137
138     @rtype: Archive
139     @return: Archive object for the given name (None if not present)
140
141     """
142     archive = archive.lower()
143     if session is None:
144         session = DBConn().session()
145     q = session.query(Archive).filter_by(archive_name=archive)
146     if q.count() == 0:
147         return None
148     return q.one()
149
150 __all__.append('get_archive')
151
152 ################################################################################
153
154 class BinAssociation(object):
155     def __init__(self, *args, **kwargs):
156         pass
157
158     def __repr__(self):
159         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
160
161 __all__.append('BinAssociation')
162
163 ################################################################################
164
165 class DBBinary(object):
166     def __init__(self, *args, **kwargs):
167         pass
168
169     def __repr__(self):
170         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
171
172 __all__.append('DBBinary')
173
174 def get_binary_from_id(id, session=None):
175     """
176     Returns DBBinary object for given C{id}
177
178     @type id: int
179     @param id: Id of the required binary
180
181     @type session: Session
182     @param session: Optional SQLA session object (a temporary one will be
183     generated if not supplied)
184
185     @rtype: DBBinary
186     @return: DBBinary object for the given binary (None if not present)
187     """
188     if session is None:
189         session = DBConn().session()
190     q = session.query(DBBinary).filter_by(binary_id=id)
191     if q.count() == 0:
192         return None
193     return q.one()
194
195 __all__.append('get_binary_from_id')
196
197 def get_binaries_from_name(package, version=None, architecture=None, session=None):
198     """
199     Returns list of DBBinary objects for given C{package} name
200
201     @type package: str
202     @param package: DBBinary package name to search for
203
204     @type version: str or None
205     @param version: Version to search for (or None)
206
207     @type package: str, list or None
208     @param package: Architectures to limit to (or None if no limit)
209
210     @type session: Session
211     @param session: Optional SQL session object (a temporary one will be
212     generated if not supplied)
213
214     @rtype: list
215     @return: list of DBBinary objects for the given name (may be empty)
216     """
217     if session is None:
218         session = DBConn().session()
219
220     q = session.query(DBBinary).filter_by(package=package)
221
222     if version is not None:
223         q = q.filter_by(version=version)
224
225     if architecture is not None:
226         if not isinstance(architecture, list):
227             architecture = [architecture]
228         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
229
230     return q.all()
231
232 __all__.append('get_binaries_from_name')
233
234 def get_binaries_from_source_id(source_id, session=None):
235     """
236     Returns list of DBBinary objects for given C{source_id}
237
238     @type source_id: int
239     @param source_id: source_id to search for
240
241     @type session: Session
242     @param session: Optional SQL session object (a temporary one will be
243     generated if not supplied)
244
245     @rtype: list
246     @return: list of DBBinary objects for the given name (may be empty)
247     """
248     if session is None:
249         session = DBConn().session()
250     return session.query(DBBinary).filter_by(source_id=source_id).all()
251
252 __all__.append('get_binaries_from_source_id')
253
254
255 def get_binary_from_name_suite(package, suitename, session=None):
256     ### For dak examine-package
257     ### XXX: Doesn't use object API yet
258     if session is None:
259         session = DBConn().session()
260
261     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
262              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
263              WHERE b.package=:package
264                AND b.file = fi.id
265                AND fi.location = l.id
266                AND l.component = c.id
267                AND ba.bin=b.id
268                AND ba.suite = su.id
269                AND su.suite_name=:suitename
270           ORDER BY b.version DESC"""
271
272     return session.execute(sql, {'package': package, 'suitename': suitename})
273
274 __all__.append('get_binary_from_name_suite')
275
276 def get_binary_components(package, suitename, arch, session=None):
277 # Check for packages that have moved from one component to another
278     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
279     WHERE b.package=:package AND s.suite_name=:suitename
280       AND (a.arch_string = :arch OR a.arch_string = 'all')
281       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
282       AND f.location = l.id
283       AND l.component = c.id
284       AND b.file = f.id"""
285
286     vals = {'package': package, 'suitename': suitename, 'arch': arch}
287
288     if session is None:
289         session = DBConn().session()
290     return session.execute(query, vals)
291
292 __all__.append('get_binary_components')
293
294 ################################################################################
295
296 class Component(object):
297     def __init__(self, *args, **kwargs):
298         pass
299
300     def __repr__(self):
301         return '<Component %s>' % self.component_name
302
303
304 __all__.append('Component')
305
306 def get_component(component, session=None):
307     """
308     Returns database id for given C{component}.
309
310     @type component: string
311     @param component: The name of the override type
312
313     @rtype: int
314     @return: the database id for the given component
315
316     """
317     component = component.lower()
318     if session is None:
319         session = DBConn().session()
320     q = session.query(Component).filter_by(component_name=component)
321     if q.count() == 0:
322         return None
323     return q.one()
324
325 __all__.append('get_component')
326
327 ################################################################################
328
329 class DBConfig(object):
330     def __init__(self, *args, **kwargs):
331         pass
332
333     def __repr__(self):
334         return '<DBConfig %s>' % self.name
335
336 __all__.append('DBConfig')
337
338 ################################################################################
339
340 class ContentFilename(object):
341     def __init__(self, *args, **kwargs):
342         pass
343
344     def __repr__(self):
345         return '<ContentFilename %s>' % self.filename
346
347 __all__.append('ContentFilename')
348
349 def get_or_set_contents_file_id(filename, session=None):
350     """
351     Returns database id for given filename.
352
353     If no matching file is found, a row is inserted.
354
355     @type filename: string
356     @param filename: The filename
357     @type session: SQLAlchemy
358     @param session: Optional SQL session object (a temporary one will be
359     generated if not supplied).  If not passed, a commit will be performed at
360     the end of the function, otherwise the caller is responsible for commiting.
361
362     @rtype: int
363     @return: the database id for the given component
364     """
365     privatetrans = False
366     if session is None:
367         session = DBConn().session()
368         privatetrans = True
369
370     try:
371         q = session.query(ContentFilename).filter_by(filename=filename)
372         if q.count() < 1:
373             cf = ContentFilename()
374             cf.filename = filename
375             session.add(cf)
376             if privatetrans:
377                 session.commit()
378             return cf.cafilename_id
379         else:
380             return q.one().cafilename_id
381
382     except:
383         traceback.print_exc()
384         raise
385
386 __all__.append('get_or_set_contents_file_id')
387
388 def get_contents(suite, overridetype, section=None, session=None):
389     """
390     Returns contents for a suite / overridetype combination, limiting
391     to a section if not None.
392
393     @type suite: Suite
394     @param suite: Suite object
395
396     @type overridetype: OverrideType
397     @param overridetype: OverrideType object
398
399     @type section: Section
400     @param section: Optional section object to limit results to
401
402     @type session: SQLAlchemy
403     @param session: Optional SQL session object (a temporary one will be
404     generated if not supplied)
405
406     @rtype: ResultsProxy
407     @return: ResultsProxy object set up to return tuples of (filename, section,
408     package, arch_id)
409     """
410
411     if session is None:
412         session = DBConn().session()
413
414     # find me all of the contents for a given suite
415     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
416                             s.section,
417                             b.package,
418                             b.architecture
419                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
420                    JOIN content_file_names n ON (c.filename=n.id)
421                    JOIN binaries b ON (b.id=c.binary_pkg)
422                    JOIN override o ON (o.package=b.package)
423                    JOIN section s ON (s.id=o.section)
424                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
425                    AND b.type=:overridetypename"""
426
427     vals = {'suiteid': suite.suite_id,
428             'overridetypeid': overridetype.overridetype_id,
429             'overridetypename': overridetype.overridetype}
430
431     if section is not None:
432         contents_q += " AND s.id = :sectionid"
433         vals['sectionid'] = section.section_id
434
435     contents_q += " ORDER BY fn"
436
437     return session.execute(contents_q, vals)
438
439 __all__.append('get_contents')
440
441 ################################################################################
442
443 class ContentFilepath(object):
444     def __init__(self, *args, **kwargs):
445         pass
446
447     def __repr__(self):
448         return '<ContentFilepath %s>' % self.filepath
449
450 __all__.append('ContentFilepath')
451
452 def get_or_set_contents_path_id(filepath, session):
453     """
454     Returns database id for given path.
455
456     If no matching file is found, a row is inserted.
457
458     @type filename: string
459     @param filename: The filepath
460     @type session: SQLAlchemy
461     @param session: Optional SQL session object (a temporary one will be
462     generated if not supplied).  If not passed, a commit will be performed at
463     the end of the function, otherwise the caller is responsible for commiting.
464
465     @rtype: int
466     @return: the database id for the given path
467     """
468     privatetrans = False
469     if session is None:
470         session = DBConn().session()
471         privatetrans = True
472
473     try:
474         q = session.query(ContentFilepath).filter_by(filepath=filepath)
475         if q.count() < 1:
476             cf = ContentFilepath()
477             cf.filepath = filepath
478             session.add(cf)
479             if privatetrans:
480                 session.commit()
481             return cf.cafilepath_id
482         else:
483             return q.one().cafilepath_id
484
485     except:
486         traceback.print_exc()
487         raise
488
489 __all__.append('get_or_set_contents_path_id')
490
491 ################################################################################
492
493 class ContentAssociation(object):
494     def __init__(self, *args, **kwargs):
495         pass
496
497     def __repr__(self):
498         return '<ContentAssociation %s>' % self.ca_id
499
500 __all__.append('ContentAssociation')
501
502 def insert_content_paths(binary_id, fullpaths, session=None):
503     """
504     Make sure given path is associated with given binary id
505
506     @type binary_id: int
507     @param binary_id: the id of the binary
508     @type fullpaths: list
509     @param fullpaths: the list of paths of the file being associated with the binary
510     @type session: SQLAlchemy session
511     @param session: Optional SQLAlchemy session.  If this is passed, the caller
512     is responsible for ensuring a transaction has begun and committing the
513     results or rolling back based on the result code.  If not passed, a commit
514     will be performed at the end of the function, otherwise the caller is
515     responsible for commiting.
516
517     @return: True upon success
518     """
519
520     privatetrans = False
521
522     if session is None:
523         session = DBConn().session()
524         privatetrans = True
525
526     try:
527         for fullpath in fullpaths:
528             (path, file) = os.path.split(fullpath)
529
530             # Get the necessary IDs ...
531             ca = ContentAssociation()
532             ca.binary_id = binary_id
533             ca.filename_id = get_or_set_contents_file_id(file)
534             ca.filepath_id = get_or_set_contents_path_id(path)
535             session.add(ca)
536
537         # Only commit if we set up the session ourself
538         if privatetrans:
539             session.commit()
540
541         return True
542     except:
543         traceback.print_exc()
544
545         # Only rollback if we set up the session ourself
546         if privatetrans:
547             session.rollback()
548
549         return False
550
551 __all__.append('insert_content_paths')
552
553 ################################################################################
554
555 class DSCFile(object):
556     def __init__(self, *args, **kwargs):
557         pass
558
559     def __repr__(self):
560         return '<DSCFile %s>' % self.dscfile_id
561
562 __all__.append('DSCFile')
563
564 ################################################################################
565
566 class PoolFile(object):
567     def __init__(self, *args, **kwargs):
568         pass
569
570     def __repr__(self):
571         return '<PoolFile %s>' % self.filename
572
573 __all__.append('PoolFile')
574
575 def get_poolfile_by_name(filename, location_id=None, session=None):
576     """
577     Returns an array of PoolFile objects for the given filename and
578     (optionally) location_id
579
580     @type filename: string
581     @param filename: the filename of the file to check against the DB
582
583     @type location_id: int
584     @param location_id: the id of the location to look in (optional)
585
586     @rtype: array
587     @return: array of PoolFile objects
588     """
589
590     if session is not None:
591         session = DBConn().session()
592
593     q = session.query(PoolFile).filter_by(filename=filename)
594
595     if location_id is not None:
596         q = q.join(Location).filter_by(location_id=location_id)
597
598     return q.all()
599
600 __all__.append('get_poolfile_by_name')
601
602 def get_poolfile_like_name(filename, session=None):
603     """
604     Returns an array of PoolFile objects which are like the given name
605
606     @type filename: string
607     @param filename: the filename of the file to check against the DB
608
609     @rtype: array
610     @return: array of PoolFile objects
611     """
612
613     if session is not None:
614         session = DBConn().session()
615
616     # TODO: There must be a way of properly using bind parameters with %FOO%
617     q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
618
619     return q.all()
620
621 __all__.append('get_poolfile_like_name')
622
623 ################################################################################
624
625 class Fingerprint(object):
626     def __init__(self, *args, **kwargs):
627         pass
628
629     def __repr__(self):
630         return '<Fingerprint %s>' % self.fingerprint
631
632 __all__.append('Fingerprint')
633
634 ################################################################################
635
636 class Keyring(object):
637     def __init__(self, *args, **kwargs):
638         pass
639
640     def __repr__(self):
641         return '<Keyring %s>' % self.keyring_name
642
643 __all__.append('Keyring')
644
645 ################################################################################
646
647 class Location(object):
648     def __init__(self, *args, **kwargs):
649         pass
650
651     def __repr__(self):
652         return '<Location %s (%s)>' % (self.path, self.location_id)
653
654 __all__.append('Location')
655
656 def get_location(location, component=None, archive=None, session=None):
657     """
658     Returns Location object for the given combination of location, component
659     and archive
660
661     @type location: string
662     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
663
664     @type component: string
665     @param component: the component name (if None, no restriction applied)
666
667     @type archive: string
668     @param archive_id: the archive name (if None, no restriction applied)
669
670     @rtype: Location / None
671     @return: Either a Location object or None if one can't be found
672     """
673
674     if session is None:
675         session = DBConn().session()
676
677     q = session.query(Location).filter_by(path=location)
678
679     if archive is not None:
680         q = q.join(Archive).filter_by(archive_name=archive)
681
682     if component is not None:
683         q = q.join(Component).filter_by(component_name=component)
684
685     if q.count() < 1:
686         return None
687     else:
688         return q.one()
689
690 __all__.append('get_location')
691
692 ################################################################################
693
694 class Maintainer(object):
695     def __init__(self, *args, **kwargs):
696         pass
697
698     def __repr__(self):
699         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
700
701     def get_split_maintainer(self):
702         if not hasattr(self, 'name') or self.name is None:
703             return ('', '', '', '')
704
705         return fix_maintainer(self.name.strip())
706
707 __all__.append('Maintainer')
708
709 ################################################################################
710
711 class NewComment(object):
712     def __init__(self, *args, **kwargs):
713         pass
714
715     def __repr__(self):
716         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
717
718 __all__.append('NewComment')
719
720 def has_new_comment(package, version, session=None):
721     """
722     Returns true if the given combination of C{package}, C{version} has a comment.
723
724     @type package: string
725     @param package: name of the package
726
727     @type version: string
728     @param version: package version
729
730     @type session: Session
731     @param session: Optional SQLA session object (a temporary one will be
732     generated if not supplied)
733
734     @rtype: boolean
735     @return: true/false
736     """
737
738     if session is None:
739         session = DBConn().session()
740
741     q = session.query(NewComment)
742     q = q.filter_by(package=package)
743     q = q.filter_by(version=version)
744     return q.count() > 0
745
746 __all__.append('has_new_comment')
747
748 def get_new_comments(package=None, version=None, comment_id=None, session=None):
749     """
750     Returns (possibly empty) list of NewComment objects for the given
751     parameters
752
753     @type package: string (optional)
754     @param package: name of the package
755
756     @type version: string (optional)
757     @param version: package version
758
759     @type comment_id: int (optional)
760     @param comment_id: An id of a comment
761
762     @type session: Session
763     @param session: Optional SQLA session object (a temporary one will be
764     generated if not supplied)
765
766     @rtype: list
767     @return: A (possibly empty) list of NewComment objects will be returned
768
769     """
770
771     if session is None:
772         session = DBConn().session()
773
774     q = session.query(NewComment)
775     if package is not None: q = q.filter_by(package=package)
776     if version is not None: q = q.filter_by(version=version)
777     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
778
779     return q.all()
780
781 __all__.append('get_new_comments')
782
783 ################################################################################
784
785 class Override(object):
786     def __init__(self, *args, **kwargs):
787         pass
788
789     def __repr__(self):
790         return '<Override %s (%s)>' % (self.package, self.suite_id)
791
792 __all__.append('Override')
793
794 def get_override(package, suite=None, component=None, overridetype=None, session=None):
795     """
796     Returns Override object for the given parameters
797
798     @type package: string
799     @param package: The name of the package
800
801     @type suite: string, list or None
802     @param suite: The name of the suite (or suites if a list) to limit to.  If
803                   None, don't limit.  Defaults to None.
804
805     @type component: string, list or None
806     @param component: The name of the component (or components if a list) to
807                       limit to.  If None, don't limit.  Defaults to None.
808
809     @type overridetype: string, list or None
810     @param overridetype: The name of the overridetype (or overridetypes if a list) to
811                          limit to.  If None, don't limit.  Defaults to None.
812
813     @type session: Session
814     @param session: Optional SQLA session object (a temporary one will be
815     generated if not supplied)
816
817     @rtype: list
818     @return: A (possibly empty) list of Override objects will be returned
819
820     """
821     if session is None:
822         session = DBConn().session()
823
824     q = session.query(Override)
825     q = q.filter_by(package=package)
826
827     if suite is not None:
828         if not isinstance(suite, list): suite = [suite]
829         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
830
831     if component is not None:
832         if not isinstance(component, list): component = [component]
833         q = q.join(Component).filter(Component.component_name.in_(component))
834
835     if overridetype is not None:
836         if not isinstance(overridetype, list): overridetype = [overridetype]
837         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
838
839     return q.all()
840
841 __all__.append('get_override')
842
843
844 ################################################################################
845
846 class OverrideType(object):
847     def __init__(self, *args, **kwargs):
848         pass
849
850     def __repr__(self):
851         return '<OverrideType %s>' % self.overridetype
852
853 __all__.append('OverrideType')
854
855 def get_override_type(override_type, session=None):
856     """
857     Returns OverrideType object for given C{override type}.
858
859     @type override_type: string
860     @param override_type: The name of the override type
861
862     @type session: Session
863     @param session: Optional SQLA session object (a temporary one will be
864     generated if not supplied)
865
866     @rtype: int
867     @return: the database id for the given override type
868
869     """
870     if session is None:
871         session = DBConn().session()
872     q = session.query(OverrideType).filter_by(overridetype=override_type)
873     if q.count() == 0:
874         return None
875     return q.one()
876
877 __all__.append('get_override_type')
878
879 ################################################################################
880
881 class PendingContentAssociation(object):
882     def __init__(self, *args, **kwargs):
883         pass
884
885     def __repr__(self):
886         return '<PendingContentAssociation %s>' % self.pca_id
887
888 __all__.append('PendingContentAssociation')
889
890 def insert_pending_content_paths(package, fullpaths, session=None):
891     """
892     Make sure given paths are temporarily associated with given
893     package
894
895     @type package: dict
896     @param package: the package to associate with should have been read in from the binary control file
897     @type fullpaths: list
898     @param fullpaths: the list of paths of the file being associated with the binary
899     @type session: SQLAlchemy session
900     @param session: Optional SQLAlchemy session.  If this is passed, the caller
901     is responsible for ensuring a transaction has begun and committing the
902     results or rolling back based on the result code.  If not passed, a commit
903     will be performed at the end of the function
904
905     @return: True upon success, False if there is a problem
906     """
907
908     privatetrans = False
909
910     if session is None:
911         session = DBConn().session()
912         privatetrans = True
913
914     try:
915         arch = get_architecture(package['Architecture'], session)
916         arch_id = arch.arch_id
917
918         # Remove any already existing recorded files for this package
919         q = session.query(PendingContentAssociation)
920         q = q.filter_by(package=package['Package'])
921         q = q.filter_by(version=package['Version'])
922         q = q.filter_by(architecture=arch_id)
923         q.delete()
924
925         # Insert paths
926         for fullpath in fullpaths:
927             (path, file) = os.path.split(fullpath)
928
929             if path.startswith( "./" ):
930                 path = path[2:]
931
932             pca = PendingContentAssociation()
933             pca.package = package['Package']
934             pca.version = package['Version']
935             pca.filename_id = get_or_set_contents_file_id(file, session)
936             pca.filepath_id = get_or_set_contents_path_id(path, session)
937             pca.architecture = arch_id
938             session.add(pca)
939
940         # Only commit if we set up the session ourself
941         if privatetrans:
942             session.commit()
943
944         return True
945     except:
946         traceback.print_exc()
947
948         # Only rollback if we set up the session ourself
949         if privatetrans:
950             session.rollback()
951
952         return False
953
954 __all__.append('insert_pending_content_paths')
955
956 ################################################################################
957
958 class Priority(object):
959     def __init__(self, *args, **kwargs):
960         pass
961
962     def __repr__(self):
963         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
964
965 __all__.append('Priority')
966
967 def get_priority(priority, session=None):
968     """
969     Returns Priority object for given C{priority name}.
970
971     @type priority: string
972     @param priority: The name of the priority
973
974     @type session: Session
975     @param session: Optional SQLA session object (a temporary one will be
976     generated if not supplied)
977
978     @rtype: Priority
979     @return: Priority object for the given priority
980
981     """
982     if session is None:
983         session = DBConn().session()
984     q = session.query(Priority).filter_by(priority=priority)
985     if q.count() == 0:
986         return None
987     return q.one()
988
989 __all__.append('get_priority')
990
991 ################################################################################
992
993 class Queue(object):
994     def __init__(self, *args, **kwargs):
995         pass
996
997     def __repr__(self):
998         return '<Queue %s>' % self.queue_name
999
1000     def autobuild_upload(self, changes, srcpath, session=None):
1001         """
1002         Update queue_build database table used for incoming autobuild support.
1003
1004         @type changes: Changes
1005         @param changes: changes object for the upload to process
1006
1007         @type srcpath: string
1008         @param srcpath: path for the queue file entries/link destinations
1009
1010         @type session: SQLAlchemy session
1011         @param session: Optional SQLAlchemy session.  If this is passed, the
1012         caller is responsible for ensuring a transaction has begun and
1013         committing the results or rolling back based on the result code.  If
1014         not passed, a commit will be performed at the end of the function,
1015         otherwise the caller is responsible for commiting.
1016
1017         @rtype: NoneType or string
1018         @return: None if the operation failed, a string describing the error if not
1019         """
1020
1021         localcommit = False
1022         if session is None:
1023             session = DBConn().session()
1024             localcommit = True
1025
1026         # TODO: Remove by moving queue config into the database
1027         conf = Config()
1028
1029         for suitename in changes.changes["distribution"].keys():
1030             # TODO: Move into database as:
1031             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1032             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1033             #       This also gets rid of the SecurityQueueBuild hack below
1034             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1035                 continue
1036
1037             # Find suite object
1038             s = get_suite(suitename, session)
1039             if s is None:
1040                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1041
1042             # TODO: Get from database as above
1043             dest_dir = conf["Dir::QueueBuild"]
1044
1045             # TODO: Move into database as above
1046             if conf.FindB("Dinstall::SecurityQueueBuild"):
1047                 dest_dir = os.path.join(dest_dir, suitename)
1048
1049             for file_entry in changes.files.keys():
1050                 src = os.path.join(srcpath, file_entry)
1051                 dest = os.path.join(dest_dir, file_entry)
1052
1053                 # TODO: Move into database as above
1054                 if Cnf.FindB("Dinstall::SecurityQueueBuild"):
1055                     # Copy it since the original won't be readable by www-data
1056                     utils.copy(src, dest)
1057                 else:
1058                     # Create a symlink to it
1059                     os.symlink(src, dest)
1060
1061                 qb = QueueBuild()
1062                 qb.suite_id = s.suite_id
1063                 qb.queue_id = self.queue_id
1064                 qb.filename = dest
1065                 qb.in_queue = True
1066
1067                 session.add(qb)
1068
1069             # If the .orig.tar.gz is in the pool, create a symlink to
1070             # it (if one doesn't already exist)
1071             if changes.orig_tar_id:
1072                 # Determine the .orig.tar.gz file name
1073                 for dsc_file in changes.dsc_files.keys():
1074                     if dsc_file.endswith(".orig.tar.gz"):
1075                         filename = dsc_file
1076
1077                 dest = os.path.join(dest_dir, filename)
1078
1079                 # If it doesn't exist, create a symlink
1080                 if not os.path.exists(dest):
1081                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1082                                         {'id': changes.orig_tar_id})
1083                     res = q.fetchone()
1084                     if not res:
1085                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1086
1087                     src = os.path.join(res[0], res[1])
1088                     os.symlink(src, dest)
1089
1090                     # Add it to the list of packages for later processing by apt-ftparchive
1091                     qb = QueueBuild()
1092                     qb.suite_id = s.suite_id
1093                     qb.queue_id = self.queue_id
1094                     qb.filename = dest
1095                     qb.in_queue = True
1096                     session.add(qb)
1097
1098                 # If it does, update things to ensure it's not removed prematurely
1099                 else:
1100                     qb = get_queue_build(dest, suite_id, session)
1101                     if qb is None:
1102                         qb.in_queue = True
1103                         qb.last_used = None
1104                         session.add(qb)
1105
1106         if localcommit:
1107             session.commit()
1108
1109         return None
1110
1111 __all__.append('Queue')
1112
1113 def get_queue(queuename, session=None):
1114     """
1115     Returns Queue object for given C{queue name}.
1116
1117     @type queuename: string
1118     @param queuename: The name of the queue
1119
1120     @type session: Session
1121     @param session: Optional SQLA session object (a temporary one will be
1122     generated if not supplied)
1123
1124     @rtype: Queue
1125     @return: Queue object for the given queue
1126
1127     """
1128     if session is None:
1129         session = DBConn().session()
1130     q = session.query(Queue).filter_by(queue_name=queuename)
1131     if q.count() == 0:
1132         return None
1133     return q.one()
1134
1135 __all__.append('get_queue')
1136
1137 ################################################################################
1138
1139 class QueueBuild(object):
1140     def __init__(self, *args, **kwargs):
1141         pass
1142
1143     def __repr__(self):
1144         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1145
1146 __all__.append('QueueBuild')
1147
1148 def get_queue_build(filename, suite_id, session=None):
1149     """
1150     Returns QueueBuild object for given C{filename} and C{suite id}.
1151
1152     @type filename: string
1153     @param filename: The name of the file
1154
1155     @type suiteid: int
1156     @param suiteid: Suite ID
1157
1158     @type session: Session
1159     @param session: Optional SQLA session object (a temporary one will be
1160     generated if not supplied)
1161
1162     @rtype: Queue
1163     @return: Queue object for the given queue
1164
1165     """
1166     if session is None:
1167         session = DBConn().session()
1168     q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite_id)
1169     if q.count() == 0:
1170         return None
1171     return q.one()
1172
1173 __all__.append('get_queue_build')
1174
1175 ################################################################################
1176
1177 class Section(object):
1178     def __init__(self, *args, **kwargs):
1179         pass
1180
1181     def __repr__(self):
1182         return '<Section %s>' % self.section
1183
1184 __all__.append('Section')
1185
1186 def get_section(section, session=None):
1187     """
1188     Returns Section object for given C{section name}.
1189
1190     @type section: string
1191     @param section: The name of the section
1192
1193     @type session: Session
1194     @param session: Optional SQLA session object (a temporary one will be
1195     generated if not supplied)
1196
1197     @rtype: Section
1198     @return: Section object for the given section name
1199
1200     """
1201     if session is None:
1202         session = DBConn().session()
1203     q = session.query(Section).filter_by(section=section)
1204     if q.count() == 0:
1205         return None
1206     return q.one()
1207
1208 __all__.append('get_section')
1209
1210 ################################################################################
1211
1212 class DBSource(object):
1213     def __init__(self, *args, **kwargs):
1214         pass
1215
1216     def __repr__(self):
1217         return '<DBSource %s (%s)>' % (self.source, self.version)
1218
1219 __all__.append('DBSource')
1220
1221 def source_exists(source, source_version, suites = ["any"], session=None):
1222     """
1223     Ensure that source exists somewhere in the archive for the binary
1224     upload being processed.
1225       1. exact match     => 1.0-3
1226       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1227
1228     @type package: string
1229     @param package: package source name
1230
1231     @type source_version: string
1232     @param source_version: expected source version
1233
1234     @type suites: list
1235     @param suites: list of suites to check in, default I{any}
1236
1237     @type session: Session
1238     @param session: Optional SQLA session object (a temporary one will be
1239     generated if not supplied)
1240
1241     @rtype: int
1242     @return: returns 1 if a source with expected version is found, otherwise 0
1243
1244     """
1245
1246     if session is None:
1247         session = DBConn().session()
1248
1249     cnf = Config()
1250
1251     for suite in suites:
1252         q = session.query(DBSource).filter_by(source=source)
1253         if suite != "any":
1254             # source must exist in suite X, or in some other suite that's
1255             # mapped to X, recursively... silent-maps are counted too,
1256             # unreleased-maps aren't.
1257             maps = cnf.ValueList("SuiteMappings")[:]
1258             maps.reverse()
1259             maps = [ m.split() for m in maps ]
1260             maps = [ (x[1], x[2]) for x in maps
1261                             if x[0] == "map" or x[0] == "silent-map" ]
1262             s = [suite]
1263             for x in maps:
1264                 if x[1] in s and x[0] not in s:
1265                     s.append(x[0])
1266
1267             q = q.join(SrcAssociation).join(Suite)
1268             q = q.filter(Suite.suite_name.in_(s))
1269
1270         # Reduce the query results to a list of version numbers
1271         ql = [ j.version for j in q.all() ]
1272
1273         # Try (1)
1274         if source_version in ql:
1275             continue
1276
1277         # Try (2)
1278         from daklib.regexes import re_bin_only_nmu
1279         orig_source_version = re_bin_only_nmu.sub('', source_version)
1280         if orig_source_version in ql:
1281             continue
1282
1283         # No source found so return not ok
1284         return 0
1285
1286     # We're good
1287     return 1
1288
1289 __all__.append('source_exists')
1290
1291 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1292     """
1293     Returns list of DBSource objects for given C{source} name and other parameters
1294
1295     @type source: str
1296     @param source: DBSource package name to search for
1297
1298     @type source: str or None
1299     @param source: DBSource version name to search for or None if not applicable
1300
1301     @type dm_upload_allowed: bool
1302     @param dm_upload_allowed: If None, no effect.  If True or False, only
1303     return packages with that dm_upload_allowed setting
1304
1305     @type session: Session
1306     @param session: Optional SQL session object (a temporary one will be
1307     generated if not supplied)
1308
1309     @rtype: list
1310     @return: list of DBSource objects for the given name (may be empty)
1311     """
1312     if session is None:
1313         session = DBConn().session()
1314
1315     q = session.query(DBSource).filter_by(source=source)
1316
1317     if version is not None:
1318         q = q.filter_by(version=version)
1319
1320     if dm_upload_allowed is not None:
1321         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1322
1323     return q.all()
1324
1325 __all__.append('get_sources_from_name')
1326
1327 def get_source_in_suite(source, suite, session=None):
1328     """
1329     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1330
1331       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1332       - B{suite} - a suite name, eg. I{unstable}
1333
1334     @type source: string
1335     @param source: source package name
1336
1337     @type suite: string
1338     @param suite: the suite name
1339
1340     @rtype: string
1341     @return: the version for I{source} in I{suite}
1342
1343     """
1344     if session is None:
1345         session = DBConn().session()
1346     q = session.query(SrcAssociation)
1347     q = q.join('source').filter_by(source=source)
1348     q = q.join('suite').filter_by(suite_name=suite)
1349     if q.count() == 0:
1350         return None
1351     # ???: Maybe we should just return the SrcAssociation object instead
1352     return q.one().source
1353
1354 __all__.append('get_source_in_suite')
1355
1356 ################################################################################
1357
1358 class SrcAssociation(object):
1359     def __init__(self, *args, **kwargs):
1360         pass
1361
1362     def __repr__(self):
1363         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1364
1365 __all__.append('SrcAssociation')
1366
1367 ################################################################################
1368
1369 class SrcUploader(object):
1370     def __init__(self, *args, **kwargs):
1371         pass
1372
1373     def __repr__(self):
1374         return '<SrcUploader %s>' % self.uploader_id
1375
1376 __all__.append('SrcUploader')
1377
1378 ################################################################################
1379
1380 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1381                  ('SuiteID', 'suite_id'),
1382                  ('Version', 'version'),
1383                  ('Origin', 'origin'),
1384                  ('Label', 'label'),
1385                  ('Description', 'description'),
1386                  ('Untouchable', 'untouchable'),
1387                  ('Announce', 'announce'),
1388                  ('Codename', 'codename'),
1389                  ('OverrideCodename', 'overridecodename'),
1390                  ('ValidTime', 'validtime'),
1391                  ('Priority', 'priority'),
1392                  ('NotAutomatic', 'notautomatic'),
1393                  ('CopyChanges', 'copychanges'),
1394                  ('CopyDotDak', 'copydotdak'),
1395                  ('CommentsDir', 'commentsdir'),
1396                  ('OverrideSuite', 'overridesuite'),
1397                  ('ChangelogBase', 'changelogbase')]
1398
1399
1400 class Suite(object):
1401     def __init__(self, *args, **kwargs):
1402         pass
1403
1404     def __repr__(self):
1405         return '<Suite %s>' % self.suite_name
1406
1407     def details(self):
1408         ret = []
1409         for disp, field in SUITE_FIELDS:
1410             val = getattr(self, field, None)
1411             if val is not None:
1412                 ret.append("%s: %s" % (disp, val))
1413
1414         return "\n".join(ret)
1415
1416 __all__.append('Suite')
1417
1418 def get_suite_architecture(suite, architecture, session=None):
1419     """
1420     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1421     doesn't exist
1422
1423     @type suite: str
1424     @param suite: Suite name to search for
1425
1426     @type architecture: str
1427     @param architecture: Architecture name to search for
1428
1429     @type session: Session
1430     @param session: Optional SQL session object (a temporary one will be
1431     generated if not supplied)
1432
1433     @rtype: SuiteArchitecture
1434     @return: the SuiteArchitecture object or None
1435     """
1436
1437     if session is None:
1438         session = DBConn().session()
1439
1440     q = session.query(SuiteArchitecture)
1441     q = q.join(Architecture).filter_by(arch_string=architecture)
1442     q = q.join(Suite).filter_by(suite_name=suite)
1443     if q.count() == 0:
1444         return None
1445     return q.one()
1446
1447 __all__.append('get_suite_architecture')
1448
1449 def get_suite(suite, session=None):
1450     """
1451     Returns Suite object for given C{suite name}.
1452
1453     @type suite: string
1454     @param suite: The name of the suite
1455
1456     @type session: Session
1457     @param session: Optional SQLA session object (a temporary one will be
1458     generated if not supplied)
1459
1460     @rtype: Suite
1461     @return: Suite object for the requested suite name (None if not presenT)
1462
1463     """
1464     if session is None:
1465         session = DBConn().session()
1466     q = session.query(Suite).filter_by(suite_name=suite)
1467     if q.count() == 0:
1468         return None
1469     return q.one()
1470
1471 __all__.append('get_suite')
1472
1473 ################################################################################
1474
1475 class SuiteArchitecture(object):
1476     def __init__(self, *args, **kwargs):
1477         pass
1478
1479     def __repr__(self):
1480         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1481
1482 __all__.append('SuiteArchitecture')
1483
1484 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1485     """
1486     Returns list of Architecture objects for given C{suite} name
1487
1488     @type source: str
1489     @param source: Suite name to search for
1490
1491     @type skipsrc: boolean
1492     @param skipsrc: Whether to skip returning the 'source' architecture entry
1493     (Default False)
1494
1495     @type skipall: boolean
1496     @param skipall: Whether to skip returning the 'all' architecture entry
1497     (Default False)
1498
1499     @type session: Session
1500     @param session: Optional SQL session object (a temporary one will be
1501     generated if not supplied)
1502
1503     @rtype: list
1504     @return: list of Architecture objects for the given name (may be empty)
1505     """
1506
1507     if session is None:
1508         session = DBConn().session()
1509
1510     q = session.query(Architecture)
1511     q = q.join(SuiteArchitecture)
1512     q = q.join(Suite).filter_by(suite_name=suite)
1513     if skipsrc:
1514         q = q.filter(Architecture.arch_string != 'source')
1515     if skipall:
1516         q = q.filter(Architecture.arch_string != 'all')
1517     q = q.order_by('arch_string')
1518     return q.all()
1519
1520 __all__.append('get_suite_architectures')
1521
1522 ################################################################################
1523
1524 class Uid(object):
1525     def __init__(self, *args, **kwargs):
1526         pass
1527
1528     def __repr__(self):
1529         return '<Uid %s (%s)>' % (self.uid, self.name)
1530
1531 __all__.append('Uid')
1532
1533 def add_database_user(uidname, session=None):
1534     """
1535     Adds a database user
1536
1537     @type uidname: string
1538     @param uidname: The uid of the user to add
1539
1540     @type session: SQLAlchemy
1541     @param session: Optional SQL session object (a temporary one will be
1542     generated if not supplied).  If not passed, a commit will be performed at
1543     the end of the function, otherwise the caller is responsible for commiting.
1544
1545     @rtype: Uid
1546     @return: the uid object for the given uidname
1547     """
1548     privatetrans = False
1549     if session is None:
1550         session = DBConn().session()
1551         privatetrans = True
1552
1553     try:
1554         session.execute("CREATE USER :uid", {'uid': uidname})
1555         if privatetrans:
1556             session.commit()
1557     except:
1558         traceback.print_exc()
1559         raise
1560
1561 __all__.append('add_database_user')
1562
1563 def get_or_set_uid(uidname, session=None):
1564     """
1565     Returns uid object for given uidname.
1566
1567     If no matching uidname is found, a row is inserted.
1568
1569     @type uidname: string
1570     @param uidname: The uid to add
1571
1572     @type session: SQLAlchemy
1573     @param session: Optional SQL session object (a temporary one will be
1574     generated if not supplied).  If not passed, a commit will be performed at
1575     the end of the function, otherwise the caller is responsible for commiting.
1576
1577     @rtype: Uid
1578     @return: the uid object for the given uidname
1579     """
1580     privatetrans = False
1581     if session is None:
1582         session = DBConn().session()
1583         privatetrans = True
1584
1585     try:
1586         q = session.query(Uid).filter_by(uid=uidname)
1587         if q.count() < 1:
1588             uid = Uid()
1589             uid.uid = uidname
1590             session.add(uid)
1591             if privatetrans:
1592                 session.commit()
1593             return uid
1594         else:
1595             return q.one()
1596
1597     except:
1598         traceback.print_exc()
1599         raise
1600
1601 __all__.append('get_or_set_uid')
1602
1603
1604 def get_uid_from_fingerprint(fpr, session=None):
1605     if session is None:
1606         session = DBConn().session()
1607
1608     q = session.query(Uid)
1609     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
1610
1611     if q.count() != 1:
1612         return None
1613     else:
1614         return q.one()
1615
1616 __all__.append('get_uid_from_fingerprint')
1617
1618 ################################################################################
1619
1620 class DBConn(Singleton):
1621     """
1622     database module init.
1623     """
1624     def __init__(self, *args, **kwargs):
1625         super(DBConn, self).__init__(*args, **kwargs)
1626
1627     def _startup(self, *args, **kwargs):
1628         self.debug = False
1629         if kwargs.has_key('debug'):
1630             self.debug = True
1631         self.__createconn()
1632
1633     def __setuptables(self):
1634         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
1635         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
1636         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
1637         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
1638         self.tbl_component = Table('component', self.db_meta, autoload=True)
1639         self.tbl_config = Table('config', self.db_meta, autoload=True)
1640         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
1641         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
1642         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
1643         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
1644         self.tbl_files = Table('files', self.db_meta, autoload=True)
1645         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
1646         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
1647         self.tbl_location = Table('location', self.db_meta, autoload=True)
1648         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
1649         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
1650         self.tbl_override = Table('override', self.db_meta, autoload=True)
1651         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
1652         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
1653         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
1654         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
1655         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
1656         self.tbl_section = Table('section', self.db_meta, autoload=True)
1657         self.tbl_source = Table('source', self.db_meta, autoload=True)
1658         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
1659         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
1660         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
1661         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
1662         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
1663
1664     def __setupmappers(self):
1665         mapper(Architecture, self.tbl_architecture,
1666                properties = dict(arch_id = self.tbl_architecture.c.id))
1667
1668         mapper(Archive, self.tbl_archive,
1669                properties = dict(archive_id = self.tbl_archive.c.id,
1670                                  archive_name = self.tbl_archive.c.name))
1671
1672         mapper(BinAssociation, self.tbl_bin_associations,
1673                properties = dict(ba_id = self.tbl_bin_associations.c.id,
1674                                  suite_id = self.tbl_bin_associations.c.suite,
1675                                  suite = relation(Suite),
1676                                  binary_id = self.tbl_bin_associations.c.bin,
1677                                  binary = relation(DBBinary)))
1678
1679         mapper(DBBinary, self.tbl_binaries,
1680                properties = dict(binary_id = self.tbl_binaries.c.id,
1681                                  package = self.tbl_binaries.c.package,
1682                                  version = self.tbl_binaries.c.version,
1683                                  maintainer_id = self.tbl_binaries.c.maintainer,
1684                                  maintainer = relation(Maintainer),
1685                                  source_id = self.tbl_binaries.c.source,
1686                                  source = relation(DBSource),
1687                                  arch_id = self.tbl_binaries.c.architecture,
1688                                  architecture = relation(Architecture),
1689                                  poolfile_id = self.tbl_binaries.c.file,
1690                                  poolfile = relation(PoolFile),
1691                                  binarytype = self.tbl_binaries.c.type,
1692                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
1693                                  fingerprint = relation(Fingerprint),
1694                                  install_date = self.tbl_binaries.c.install_date,
1695                                  binassociations = relation(BinAssociation,
1696                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
1697
1698         mapper(Component, self.tbl_component,
1699                properties = dict(component_id = self.tbl_component.c.id,
1700                                  component_name = self.tbl_component.c.name))
1701
1702         mapper(DBConfig, self.tbl_config,
1703                properties = dict(config_id = self.tbl_config.c.id))
1704
1705         mapper(ContentAssociation, self.tbl_content_associations,
1706                properties = dict(ca_id = self.tbl_content_associations.c.id,
1707                                  filename_id = self.tbl_content_associations.c.filename,
1708                                  filename    = relation(ContentFilename),
1709                                  filepath_id = self.tbl_content_associations.c.filepath,
1710                                  filepath    = relation(ContentFilepath),
1711                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
1712                                  binary      = relation(DBBinary)))
1713
1714
1715         mapper(ContentFilename, self.tbl_content_file_names,
1716                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
1717                                  filename = self.tbl_content_file_names.c.file))
1718
1719         mapper(ContentFilepath, self.tbl_content_file_paths,
1720                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
1721                                  filepath = self.tbl_content_file_paths.c.path))
1722
1723         mapper(DSCFile, self.tbl_dsc_files,
1724                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
1725                                  source_id = self.tbl_dsc_files.c.source,
1726                                  source = relation(DBSource),
1727                                  poolfile_id = self.tbl_dsc_files.c.file,
1728                                  poolfile = relation(PoolFile)))
1729
1730         mapper(PoolFile, self.tbl_files,
1731                properties = dict(file_id = self.tbl_files.c.id,
1732                                  filesize = self.tbl_files.c.size,
1733                                  location_id = self.tbl_files.c.location,
1734                                  location = relation(Location)))
1735
1736         mapper(Fingerprint, self.tbl_fingerprint,
1737                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
1738                                  uid_id = self.tbl_fingerprint.c.uid,
1739                                  uid = relation(Uid),
1740                                  keyring_id = self.tbl_fingerprint.c.keyring,
1741                                  keyring = relation(Keyring)))
1742
1743         mapper(Keyring, self.tbl_keyrings,
1744                properties = dict(keyring_name = self.tbl_keyrings.c.name,
1745                                  keyring_id = self.tbl_keyrings.c.id))
1746
1747         mapper(Location, self.tbl_location,
1748                properties = dict(location_id = self.tbl_location.c.id,
1749                                  component_id = self.tbl_location.c.component,
1750                                  component = relation(Component),
1751                                  archive_id = self.tbl_location.c.archive,
1752                                  archive = relation(Archive),
1753                                  archive_type = self.tbl_location.c.type))
1754
1755         mapper(Maintainer, self.tbl_maintainer,
1756                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
1757
1758         mapper(NewComment, self.tbl_new_comments,
1759                properties = dict(comment_id = self.tbl_new_comments.c.id))
1760
1761         mapper(Override, self.tbl_override,
1762                properties = dict(suite_id = self.tbl_override.c.suite,
1763                                  suite = relation(Suite),
1764                                  component_id = self.tbl_override.c.component,
1765                                  component = relation(Component),
1766                                  priority_id = self.tbl_override.c.priority,
1767                                  priority = relation(Priority),
1768                                  section_id = self.tbl_override.c.section,
1769                                  section = relation(Section),
1770                                  overridetype_id = self.tbl_override.c.type,
1771                                  overridetype = relation(OverrideType)))
1772
1773         mapper(OverrideType, self.tbl_override_type,
1774                properties = dict(overridetype = self.tbl_override_type.c.type,
1775                                  overridetype_id = self.tbl_override_type.c.id))
1776
1777         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
1778                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
1779                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
1780                                  filepath = relation(ContentFilepath),
1781                                  filename_id = self.tbl_pending_content_associations.c.filename,
1782                                  filename = relation(ContentFilename)))
1783
1784         mapper(Priority, self.tbl_priority,
1785                properties = dict(priority_id = self.tbl_priority.c.id))
1786
1787         mapper(Queue, self.tbl_queue,
1788                properties = dict(queue_id = self.tbl_queue.c.id))
1789
1790         mapper(QueueBuild, self.tbl_queue_build,
1791                properties = dict(suite_id = self.tbl_queue_build.c.suite,
1792                                  queue_id = self.tbl_queue_build.c.queue,
1793                                  queue = relation(Queue, backref='queuebuild')))
1794
1795         mapper(Section, self.tbl_section,
1796                properties = dict(section_id = self.tbl_section.c.id))
1797
1798         mapper(DBSource, self.tbl_source,
1799                properties = dict(source_id = self.tbl_source.c.id,
1800                                  version = self.tbl_source.c.version,
1801                                  maintainer_id = self.tbl_source.c.maintainer,
1802                                  maintainer = relation(Maintainer,
1803                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
1804                                  poolfile_id = self.tbl_source.c.file,
1805                                  poolfile = relation(PoolFile),
1806                                  fingerprint_id = self.tbl_source.c.sig_fpr,
1807                                  fingerprint = relation(Fingerprint),
1808                                  changedby_id = self.tbl_source.c.changedby,
1809                                  changedby = relation(Maintainer,
1810                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
1811                                  srcfiles = relation(DSCFile,
1812                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
1813                                  srcassociations = relation(SrcAssociation,
1814                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
1815
1816         mapper(SrcAssociation, self.tbl_src_associations,
1817                properties = dict(sa_id = self.tbl_src_associations.c.id,
1818                                  suite_id = self.tbl_src_associations.c.suite,
1819                                  suite = relation(Suite),
1820                                  source_id = self.tbl_src_associations.c.source,
1821                                  source = relation(DBSource)))
1822
1823         mapper(SrcUploader, self.tbl_src_uploaders,
1824                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
1825                                  source_id = self.tbl_src_uploaders.c.source,
1826                                  source = relation(DBSource,
1827                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
1828                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
1829                                  maintainer = relation(Maintainer,
1830                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
1831
1832         mapper(Suite, self.tbl_suite,
1833                properties = dict(suite_id = self.tbl_suite.c.id))
1834
1835         mapper(SuiteArchitecture, self.tbl_suite_architectures,
1836                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
1837                                  suite = relation(Suite, backref='suitearchitectures'),
1838                                  arch_id = self.tbl_suite_architectures.c.architecture,
1839                                  architecture = relation(Architecture)))
1840
1841         mapper(Uid, self.tbl_uid,
1842                properties = dict(uid_id = self.tbl_uid.c.id,
1843                                  fingerprint = relation(Fingerprint)))
1844
1845     ## Connection functions
1846     def __createconn(self):
1847         from config import Config
1848         cnf = Config()
1849         if cnf["DB::Host"]:
1850             # TCP/IP
1851             connstr = "postgres://%s" % cnf["DB::Host"]
1852             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1853                 connstr += ":%s" % cnf["DB::Port"]
1854             connstr += "/%s" % cnf["DB::Name"]
1855         else:
1856             # Unix Socket
1857             connstr = "postgres:///%s" % cnf["DB::Name"]
1858             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1859                 connstr += "?port=%s" % cnf["DB::Port"]
1860
1861         self.db_pg   = create_engine(connstr, echo=self.debug)
1862         self.db_meta = MetaData()
1863         self.db_meta.bind = self.db_pg
1864         self.db_smaker = sessionmaker(bind=self.db_pg,
1865                                       autoflush=True,
1866                                       autocommit=False)
1867
1868         self.__setuptables()
1869         self.__setupmappers()
1870
1871     def session(self):
1872         return self.db_smaker()
1873
1874 __all__.append('DBConn')
1875
1876