]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
Alter get_maintainer API to return Maintainer object or None
[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 __eq__(self, val):
63         if isinstance(val, str):
64             return (self.arch_string== val)
65         # This signals to use the normal comparison operator
66         return NotImplemented
67
68     def __ne__(self, val):
69         if isinstance(val, str):
70             return (self.arch_string != val)
71         # This signals to use the normal comparison operator
72         return NotImplemented
73
74     def __repr__(self):
75         return '<Architecture %s>' % self.arch_string
76
77 __all__.append('Architecture')
78
79 def get_architecture(architecture, session=None):
80     """
81     Returns database id for given C{architecture}.
82
83     @type architecture: string
84     @param architecture: The name of the architecture
85
86     @type session: Session
87     @param session: Optional SQLA session object (a temporary one will be
88     generated if not supplied)
89
90     @rtype: Architecture
91     @return: Architecture object for the given arch (None if not present)
92
93     """
94     privatetrans = False
95
96     if session is None:
97         session = DBConn().session()
98         privatetrans = True
99
100     q = session.query(Architecture).filter_by(arch_string=architecture)
101
102     if q.count() == 0:
103         ret = None
104     else:
105         ret = q.one()
106
107     if privatetrans:
108         session.close()
109
110     return ret
111
112 __all__.append('get_architecture')
113
114 def get_architecture_suites(architecture, session=None):
115     """
116     Returns list of Suite objects for given C{architecture} name
117
118     @type source: str
119     @param source: Architecture name to search for
120
121     @type session: Session
122     @param session: Optional SQL session object (a temporary one will be
123     generated if not supplied)
124
125     @rtype: list
126     @return: list of Suite objects for the given name (may be empty)
127     """
128     privatetrans = False
129
130     if session is None:
131         session = DBConn().session()
132         privatetrans = True
133
134     q = session.query(Suite)
135     q = q.join(SuiteArchitecture)
136     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
137
138     ret = q.all()
139
140     if privatetrans:
141         session.close()
142
143     return ret
144
145 __all__.append('get_architecture_suites')
146
147 ################################################################################
148
149 class Archive(object):
150     def __init__(self, *args, **kwargs):
151         pass
152
153     def __repr__(self):
154         return '<Archive %s>' % self.name
155
156 __all__.append('Archive')
157
158 def get_archive(archive, session=None):
159     """
160     returns database id for given c{archive}.
161
162     @type archive: string
163     @param archive: the name of the arhive
164
165     @type session: Session
166     @param session: Optional SQLA session object (a temporary one will be
167     generated if not supplied)
168
169     @rtype: Archive
170     @return: Archive object for the given name (None if not present)
171
172     """
173     archive = archive.lower()
174
175     privatetrans = False
176     if session is None:
177         session = DBConn().session()
178         privatetrans = True
179
180     q = session.query(Archive).filter_by(archive_name=archive)
181
182     if q.count() == 0:
183         ret = None
184     else:
185         ret = q.one()
186
187     if privatetrans:
188         session.close()
189
190     return ret
191
192 __all__.append('get_archive')
193
194 ################################################################################
195
196 class BinAssociation(object):
197     def __init__(self, *args, **kwargs):
198         pass
199
200     def __repr__(self):
201         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
202
203 __all__.append('BinAssociation')
204
205 ################################################################################
206
207 class DBBinary(object):
208     def __init__(self, *args, **kwargs):
209         pass
210
211     def __repr__(self):
212         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
213
214 __all__.append('DBBinary')
215
216 def get_suites_binary_in(package, session=None):
217     """
218     Returns list of Suite objects which given C{package} name is in
219
220     @type source: str
221     @param source: DBBinary package name to search for
222
223     @rtype: list
224     @return: list of Suite objects for the given package
225     """
226
227     privatetrans = False
228     if session is None:
229         session = DBConn().session()
230         privatetrans = True
231
232     ret = session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
233
234     session.close()
235
236     return ret
237
238 __all__.append('get_suites_binary_in')
239
240 def get_binary_from_id(id, session=None):
241     """
242     Returns DBBinary object for given C{id}
243
244     @type id: int
245     @param id: Id of the required binary
246
247     @type session: Session
248     @param session: Optional SQLA session object (a temporary one will be
249     generated if not supplied)
250
251     @rtype: DBBinary
252     @return: DBBinary object for the given binary (None if not present)
253     """
254     privatetrans = False
255     if session is None:
256         session = DBConn().session()
257         privatetrans = True
258
259     q = session.query(DBBinary).filter_by(binary_id=id)
260
261     if q.count() == 0:
262         ret = None
263     else:
264         ret = q.one()
265
266     if privatetrans:
267         session.close()
268
269     return ret
270
271 __all__.append('get_binary_from_id')
272
273 def get_binaries_from_name(package, version=None, architecture=None, session=None):
274     """
275     Returns list of DBBinary objects for given C{package} name
276
277     @type package: str
278     @param package: DBBinary package name to search for
279
280     @type version: str or None
281     @param version: Version to search for (or None)
282
283     @type package: str, list or None
284     @param package: Architectures to limit to (or None if no limit)
285
286     @type session: Session
287     @param session: Optional SQL session object (a temporary one will be
288     generated if not supplied)
289
290     @rtype: list
291     @return: list of DBBinary objects for the given name (may be empty)
292     """
293     privatetrans = False
294     if session is None:
295         session = DBConn().session()
296         privatetrans = True
297
298     q = session.query(DBBinary).filter_by(package=package)
299
300     if version is not None:
301         q = q.filter_by(version=version)
302
303     if architecture is not None:
304         if not isinstance(architecture, list):
305             architecture = [architecture]
306         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
307
308     ret = q.all()
309
310     if privatetrans:
311         session.close()
312
313     return ret
314
315 __all__.append('get_binaries_from_name')
316
317 def get_binaries_from_source_id(source_id, session=None):
318     """
319     Returns list of DBBinary objects for given C{source_id}
320
321     @type source_id: int
322     @param source_id: source_id to search for
323
324     @type session: Session
325     @param session: Optional SQL session object (a temporary one will be
326     generated if not supplied)
327
328     @rtype: list
329     @return: list of DBBinary objects for the given name (may be empty)
330     """
331     privatetrans = False
332     if session is None:
333         session = DBConn().session()
334         privatetrans = True
335
336     ret = session.query(DBBinary).filter_by(source_id=source_id).all()
337
338     if privatetrans:
339         session.close()
340
341     return ret
342
343
344 __all__.append('get_binaries_from_source_id')
345
346
347 def get_binary_from_name_suite(package, suitename, session=None):
348     ### For dak examine-package
349     ### XXX: Doesn't use object API yet
350     privatetrans = False
351     if session is None:
352         session = DBConn().session()
353         privatetrans = True
354
355     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
356              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
357              WHERE b.package=:package
358                AND b.file = fi.id
359                AND fi.location = l.id
360                AND l.component = c.id
361                AND ba.bin=b.id
362                AND ba.suite = su.id
363                AND su.suite_name=:suitename
364           ORDER BY b.version DESC"""
365
366     ret = session.execute(sql, {'package': package, 'suitename': suitename})
367
368     if privatetrans:
369         session.close()
370
371     return ret
372
373 __all__.append('get_binary_from_name_suite')
374
375 def get_binary_components(package, suitename, arch, session=None):
376     # Check for packages that have moved from one component to another
377     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
378     WHERE b.package=:package AND s.suite_name=:suitename
379       AND (a.arch_string = :arch OR a.arch_string = 'all')
380       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
381       AND f.location = l.id
382       AND l.component = c.id
383       AND b.file = f.id"""
384
385     vals = {'package': package, 'suitename': suitename, 'arch': arch}
386
387     privatetrans = False
388     if session is None:
389         session = DBConn().session()
390         privatetrans = True
391
392     ret = session.execute(query, vals)
393
394     if privatetrans:
395         session.close()
396
397     return ret
398
399 __all__.append('get_binary_components')
400
401 ################################################################################
402
403 class Component(object):
404     def __init__(self, *args, **kwargs):
405         pass
406
407     def __eq__(self, val):
408         if isinstance(val, str):
409             return (self.component_name == val)
410         # This signals to use the normal comparison operator
411         return NotImplemented
412
413     def __ne__(self, val):
414         if isinstance(val, str):
415             return (self.component_name != val)
416         # This signals to use the normal comparison operator
417         return NotImplemented
418
419     def __repr__(self):
420         return '<Component %s>' % self.component_name
421
422
423 __all__.append('Component')
424
425 def get_component(component, session=None):
426     """
427     Returns database id for given C{component}.
428
429     @type component: string
430     @param component: The name of the override type
431
432     @rtype: int
433     @return: the database id for the given component
434
435     """
436     component = component.lower()
437
438     privatetrans = False
439     if session is None:
440         session = DBConn().session()
441         privatetrans = True
442
443     q = session.query(Component).filter_by(component_name=component)
444
445     if q.count() == 0:
446         ret = None
447     else:
448         ret = q.one()
449
450     if privatetrans:
451         session.close()
452
453     return ret
454
455 __all__.append('get_component')
456
457 ################################################################################
458
459 class DBConfig(object):
460     def __init__(self, *args, **kwargs):
461         pass
462
463     def __repr__(self):
464         return '<DBConfig %s>' % self.name
465
466 __all__.append('DBConfig')
467
468 ################################################################################
469
470 class ContentFilename(object):
471     def __init__(self, *args, **kwargs):
472         pass
473
474     def __repr__(self):
475         return '<ContentFilename %s>' % self.filename
476
477 __all__.append('ContentFilename')
478
479 def get_or_set_contents_file_id(filename, session=None):
480     """
481     Returns database id for given filename.
482
483     If no matching file is found, a row is inserted.
484
485     @type filename: string
486     @param filename: The filename
487     @type session: SQLAlchemy
488     @param session: Optional SQL session object (a temporary one will be
489     generated if not supplied).  If not passed, a commit will be performed at
490     the end of the function, otherwise the caller is responsible for commiting.
491
492     @rtype: int
493     @return: the database id for the given component
494     """
495     privatetrans = False
496     if session is None:
497         session = DBConn().session()
498         privatetrans = True
499
500     q = session.query(ContentFilename).filter_by(filename=filename)
501     if q.count() < 1:
502         cf = ContentFilename()
503         cf.filename = filename
504         session.add(cf)
505         if privatetrans:
506             session.commit()
507         else:
508             session.flush()
509         ret = cf.cafilename_id
510     else:
511         ret = q.one().cafilename_id
512
513     if privatetrans:
514         session.close()
515
516     return ret
517
518 __all__.append('get_or_set_contents_file_id')
519
520 def get_contents(suite, overridetype, section=None, session=None):
521     """
522     Returns contents for a suite / overridetype combination, limiting
523     to a section if not None.
524
525     @type suite: Suite
526     @param suite: Suite object
527
528     @type overridetype: OverrideType
529     @param overridetype: OverrideType object
530
531     @type section: Section
532     @param section: Optional section object to limit results to
533
534     @type session: SQLAlchemy
535     @param session: Optional SQL session object (a temporary one will be
536     generated if not supplied)
537
538     @rtype: ResultsProxy
539     @return: ResultsProxy object set up to return tuples of (filename, section,
540     package, arch_id)
541     """
542
543     privatetrans = False
544     if session is None:
545         session = DBConn().session()
546         privatetrans = True
547
548     # find me all of the contents for a given suite
549     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
550                             s.section,
551                             b.package,
552                             b.architecture
553                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
554                    JOIN content_file_names n ON (c.filename=n.id)
555                    JOIN binaries b ON (b.id=c.binary_pkg)
556                    JOIN override o ON (o.package=b.package)
557                    JOIN section s ON (s.id=o.section)
558                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
559                    AND b.type=:overridetypename"""
560
561     vals = {'suiteid': suite.suite_id,
562             'overridetypeid': overridetype.overridetype_id,
563             'overridetypename': overridetype.overridetype}
564
565     if section is not None:
566         contents_q += " AND s.id = :sectionid"
567         vals['sectionid'] = section.section_id
568
569     contents_q += " ORDER BY fn"
570
571     ret = session.execute(contents_q, vals)
572
573     if privatetrans:
574         session.close()
575
576     return ret
577
578 __all__.append('get_contents')
579
580 ################################################################################
581
582 class ContentFilepath(object):
583     def __init__(self, *args, **kwargs):
584         pass
585
586     def __repr__(self):
587         return '<ContentFilepath %s>' % self.filepath
588
589 __all__.append('ContentFilepath')
590
591 def get_or_set_contents_path_id(filepath, session=None):
592     """
593     Returns database id for given path.
594
595     If no matching file is found, a row is inserted.
596
597     @type filename: string
598     @param filename: The filepath
599     @type session: SQLAlchemy
600     @param session: Optional SQL session object (a temporary one will be
601     generated if not supplied).  If not passed, a commit will be performed at
602     the end of the function, otherwise the caller is responsible for commiting.
603
604     @rtype: int
605     @return: the database id for the given path
606     """
607     privatetrans = False
608     if session is None:
609         session = DBConn().session()
610         privatetrans = True
611
612     q = session.query(ContentFilepath).filter_by(filepath=filepath)
613     if q.count() < 1:
614         cf = ContentFilepath()
615         cf.filepath = filepath
616         session.add(cf)
617         if privatetrans:
618             session.commit()
619         else:
620             session.flush()
621         ret = cf.cafilepath_id
622     else:
623         ret = q.one().cafilepath_id
624
625     if privatetrans:
626         session.close()
627
628     return ret
629
630 __all__.append('get_or_set_contents_path_id')
631
632 ################################################################################
633
634 class ContentAssociation(object):
635     def __init__(self, *args, **kwargs):
636         pass
637
638     def __repr__(self):
639         return '<ContentAssociation %s>' % self.ca_id
640
641 __all__.append('ContentAssociation')
642
643 def insert_content_paths(binary_id, fullpaths, session=None):
644     """
645     Make sure given path is associated with given binary id
646
647     @type binary_id: int
648     @param binary_id: the id of the binary
649     @type fullpaths: list
650     @param fullpaths: the list of paths of the file being associated with the binary
651     @type session: SQLAlchemy session
652     @param session: Optional SQLAlchemy session.  If this is passed, the caller
653     is responsible for ensuring a transaction has begun and committing the
654     results or rolling back based on the result code.  If not passed, a commit
655     will be performed at the end of the function, otherwise the caller is
656     responsible for commiting.
657
658     @return: True upon success
659     """
660
661     privatetrans = False
662     if session is None:
663         session = DBConn().session()
664         privatetrans = True
665
666     try:
667         # Insert paths
668         pathcache = {}
669         for fullpath in fullpaths:
670             # Get the necessary IDs ...
671             (path, file) = os.path.split(fullpath)
672
673             filepath_id = get_or_set_contents_path_id(path, session)
674             filename_id = get_or_set_contents_file_id(file, session)
675
676             pathcache[fullpath] = (filepath_id, filename_id)
677
678         for fullpath, dat in pathcache.items():
679             ca = ContentAssociation()
680             ca.binary_id = binary_id
681             ca.filepath_id = dat[0]
682             ca.filename_id = dat[1]
683             session.add(ca)
684
685         # Only commit if we set up the session ourself
686         if privatetrans:
687             session.commit()
688             session.close()
689         else:
690             session.flush()
691
692         return True
693
694     except:
695         traceback.print_exc()
696
697         # Only rollback if we set up the session ourself
698         if privatetrans:
699             session.rollback()
700             session.close()
701
702         return False
703
704 __all__.append('insert_content_paths')
705
706 ################################################################################
707
708 class DSCFile(object):
709     def __init__(self, *args, **kwargs):
710         pass
711
712     def __repr__(self):
713         return '<DSCFile %s>' % self.dscfile_id
714
715 __all__.append('DSCFile')
716
717 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
718     """
719     Returns a list of DSCFiles which may be empty
720
721     @type dscfile_id: int (optional)
722     @param dscfile_id: the dscfile_id of the DSCFiles to find
723
724     @type source_id: int (optional)
725     @param source_id: the source id related to the DSCFiles to find
726
727     @type poolfile_id: int (optional)
728     @param poolfile_id: the poolfile id related to the DSCFiles to find
729
730     @rtype: list
731     @return: Possibly empty list of DSCFiles
732     """
733
734     privatetrans = False
735     if session is None:
736         session = DBConn().session()
737         privatetrans = True
738
739     q = session.query(DSCFile)
740
741     if dscfile_id is not None:
742         q = q.filter_by(dscfile_id=dscfile_id)
743
744     if source_id is not None:
745         q = q.filter_by(source_id=source_id)
746
747     if poolfile_id is not None:
748         q = q.filter_by(poolfile_id=poolfile_id)
749
750     ret = q.all()
751
752     if privatetrans:
753         session.close()
754
755     return ret
756
757 __all__.append('get_dscfiles')
758
759 ################################################################################
760
761 class PoolFile(object):
762     def __init__(self, *args, **kwargs):
763         pass
764
765     def __repr__(self):
766         return '<PoolFile %s>' % self.filename
767
768 __all__.append('PoolFile')
769
770 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
771     """
772     Returns a tuple:
773      (ValidFileFound [boolean or None], PoolFile object or None)
774
775     @type filename: string
776     @param filename: the filename of the file to check against the DB
777
778     @type filesize: int
779     @param filesize: the size of the file to check against the DB
780
781     @type md5sum: string
782     @param md5sum: the md5sum of the file to check against the DB
783
784     @type location_id: int
785     @param location_id: the id of the location to look in
786
787     @rtype: tuple
788     @return: Tuple of length 2.
789              If more than one file found with that name:
790                     (None,  None)
791              If valid pool file found: (True, PoolFile object)
792              If valid pool file not found:
793                     (False, None) if no file found
794                     (False, PoolFile object) if file found with size/md5sum mismatch
795     """
796
797     privatetrans = False
798     if session is None:
799         session = DBConn().session()
800         privatetrans = True
801
802     q = session.query(PoolFile).filter_by(filename=filename)
803     q = q.join(Location).filter_by(location_id=location_id)
804
805     ret = None
806
807     if q.count() > 1:
808         ret = (None, None)
809     elif q.count() < 1:
810         ret = (False, None)
811     else:
812         obj = q.one()
813         if obj.md5sum != md5sum or obj.filesize != filesize:
814             ret = (False, obj)
815
816     if ret is None:
817         ret = (True, obj)
818
819     if privatetrans:
820         session.close()
821
822     return ret
823
824 __all__.append('check_poolfile')
825
826 def get_poolfile_by_id(file_id, session=None):
827     """
828     Returns a PoolFile objects or None for the given id
829
830     @type file_id: int
831     @param file_id: the id of the file to look for
832
833     @rtype: PoolFile or None
834     @return: either the PoolFile object or None
835     """
836
837     privatetrans = False
838     if session is None:
839         session = DBConn().session()
840         privatetrans = True
841
842     q = session.query(PoolFile).filter_by(file_id=file_id)
843
844     if q.count() > 0:
845         ret = q.one()
846     else:
847         ret = None
848
849     if privatetrans:
850         session.close()
851
852     return ret
853
854 __all__.append('get_poolfile_by_id')
855
856
857 def get_poolfile_by_name(filename, location_id=None, session=None):
858     """
859     Returns an array of PoolFile objects for the given filename and
860     (optionally) location_id
861
862     @type filename: string
863     @param filename: the filename of the file to check against the DB
864
865     @type location_id: int
866     @param location_id: the id of the location to look in (optional)
867
868     @rtype: array
869     @return: array of PoolFile objects
870     """
871
872     privatetrans = False
873     if session is None:
874         session = DBConn().session()
875         privatetrans = True
876
877     q = session.query(PoolFile).filter_by(filename=filename)
878
879     if location_id is not None:
880         q = q.join(Location).filter_by(location_id=location_id)
881
882     ret = q.all()
883
884     if privatetrans:
885         session.close()
886
887     return ret
888
889 __all__.append('get_poolfile_by_name')
890
891 def get_poolfile_like_name(filename, session=None):
892     """
893     Returns an array of PoolFile objects which are like the given name
894
895     @type filename: string
896     @param filename: the filename of the file to check against the DB
897
898     @rtype: array
899     @return: array of PoolFile objects
900     """
901
902     privatetrans = False
903     if session is None:
904         session = DBConn().session()
905         privatetrans = True
906
907     # TODO: There must be a way of properly using bind parameters with %FOO%
908     q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
909
910     ret = q.all()
911
912     if privatetrans:
913         session.close()
914
915     return ret
916
917 __all__.append('get_poolfile_like_name')
918
919 ################################################################################
920
921 class Fingerprint(object):
922     def __init__(self, *args, **kwargs):
923         pass
924
925     def __repr__(self):
926         return '<Fingerprint %s>' % self.fingerprint
927
928 __all__.append('Fingerprint')
929
930 def get_or_set_fingerprint(fpr, session=None):
931     """
932     Returns Fingerprint object for given fpr.
933
934     If no matching fpr is found, a row is inserted.
935
936     @type fpr: string
937     @param fpr: The fpr to find / add
938
939     @type session: SQLAlchemy
940     @param session: Optional SQL session object (a temporary one will be
941     generated if not supplied).  If not passed, a commit will be performed at
942     the end of the function, otherwise the caller is responsible for commiting.
943     A flush will be performed either way.
944
945     @rtype: Fingerprint
946     @return: the Fingerprint object for the given fpr
947     """
948     privatetrans = False
949     if session is None:
950         session = DBConn().session()
951         privatetrans = True
952
953     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
954     if q.count() < 1:
955         fingerprint = Fingerprint()
956         fingerprint.fingerprint = fpr
957         session.add(fingerprint)
958         if privatetrans:
959             session.commit()
960         else:
961             session.flush()
962         ret = fingerprint
963     else:
964         ret = q.one()
965
966     if privatetrans:
967         session.close()
968
969     return ret
970
971 __all__.append('get_or_set_fingerprint')
972
973 ################################################################################
974
975 class Keyring(object):
976     def __init__(self, *args, **kwargs):
977         pass
978
979     def __repr__(self):
980         return '<Keyring %s>' % self.keyring_name
981
982 __all__.append('Keyring')
983
984 ################################################################################
985
986 class Location(object):
987     def __init__(self, *args, **kwargs):
988         pass
989
990     def __repr__(self):
991         return '<Location %s (%s)>' % (self.path, self.location_id)
992
993 __all__.append('Location')
994
995 def get_location(location, component=None, archive=None, session=None):
996     """
997     Returns Location object for the given combination of location, component
998     and archive
999
1000     @type location: string
1001     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1002
1003     @type component: string
1004     @param component: the component name (if None, no restriction applied)
1005
1006     @type archive: string
1007     @param archive_id: the archive name (if None, no restriction applied)
1008
1009     @rtype: Location / None
1010     @return: Either a Location object or None if one can't be found
1011     """
1012
1013     privatetrans = False
1014     if session is None:
1015         session = DBConn().session()
1016         privatetrans = True
1017
1018     q = session.query(Location).filter_by(path=location)
1019
1020     if archive is not None:
1021         q = q.join(Archive).filter_by(archive_name=archive)
1022
1023     if component is not None:
1024         q = q.join(Component).filter_by(component_name=component)
1025
1026     if q.count() < 1:
1027         ret = None
1028     else:
1029         ret = q.one()
1030
1031     if privatetrans:
1032         session.close()
1033
1034     return ret
1035
1036 __all__.append('get_location')
1037
1038 ################################################################################
1039
1040 class Maintainer(object):
1041     def __init__(self, *args, **kwargs):
1042         pass
1043
1044     def __repr__(self):
1045         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1046
1047     def get_split_maintainer(self):
1048         if not hasattr(self, 'name') or self.name is None:
1049             return ('', '', '', '')
1050
1051         return fix_maintainer(self.name.strip())
1052
1053 __all__.append('Maintainer')
1054
1055 def get_or_set_maintainer(name, session=None):
1056     """
1057     Returns Maintainer object for given maintainer name.
1058
1059     If no matching maintainer name is found, a row is inserted.
1060
1061     @type name: string
1062     @param name: The maintainer name to add
1063
1064     @type session: SQLAlchemy
1065     @param session: Optional SQL session object (a temporary one will be
1066     generated if not supplied).  If not passed, a commit will be performed at
1067     the end of the function, otherwise the caller is responsible for commiting.
1068     A flush will be performed either way.
1069
1070     @rtype: Maintainer
1071     @return: the Maintainer object for the given maintainer
1072     """
1073     privatetrans = False
1074     if session is None:
1075         session = DBConn().session()
1076         privatetrans = True
1077
1078     q = session.query(Maintainer).filter_by(name=name)
1079     if q.count() < 1:
1080         maintainer = Maintainer()
1081         maintainer.name = name
1082         session.add(maintainer)
1083         if privatetrans:
1084             session.commit()
1085         else:
1086             session.flush()
1087         ret = maintainer
1088     else:
1089         ret = q.one()
1090
1091     if privatetrans:
1092         session.close()
1093
1094     return ret
1095
1096 __all__.append('get_or_set_maintainer')
1097
1098 def get_maintainer(maintainer_id, session=None):
1099     """
1100     Return the name of the maintainer behind C{maintainer_id} or None if that
1101     maintainer_id is invalid.
1102
1103     @type maintainer_id: int
1104     @param maintainer_id: the id of the maintainer
1105
1106     @rtype: Maintainer
1107     @return: the Maintainer with this C{maintainer_id}
1108     """
1109
1110     privatetrans = False
1111     if session is None:
1112         session = DBConn().session()
1113         privatetrans = True
1114
1115     try:
1116         return session.query(Maintainer).get(maintainer_id)
1117     finally:
1118         if privatetrans:
1119             session.close()
1120
1121 __all__.append('get_maintainer')
1122
1123 ################################################################################
1124
1125 class NewComment(object):
1126     def __init__(self, *args, **kwargs):
1127         pass
1128
1129     def __repr__(self):
1130         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1131
1132 __all__.append('NewComment')
1133
1134 def has_new_comment(package, version, session=None):
1135     """
1136     Returns true if the given combination of C{package}, C{version} has a comment.
1137
1138     @type package: string
1139     @param package: name of the package
1140
1141     @type version: string
1142     @param version: package version
1143
1144     @type session: Session
1145     @param session: Optional SQLA session object (a temporary one will be
1146     generated if not supplied)
1147
1148     @rtype: boolean
1149     @return: true/false
1150     """
1151
1152     privatetrans = False
1153     if session is None:
1154         session = DBConn().session()
1155         privatetrans = True
1156
1157     q = session.query(NewComment)
1158     q = q.filter_by(package=package)
1159     q = q.filter_by(version=version)
1160
1161     ret = q.count() > 0
1162
1163     if privatetrans:
1164         session.close()
1165
1166     return ret
1167
1168 __all__.append('has_new_comment')
1169
1170 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1171     """
1172     Returns (possibly empty) list of NewComment objects for the given
1173     parameters
1174
1175     @type package: string (optional)
1176     @param package: name of the package
1177
1178     @type version: string (optional)
1179     @param version: package version
1180
1181     @type comment_id: int (optional)
1182     @param comment_id: An id of a comment
1183
1184     @type session: Session
1185     @param session: Optional SQLA session object (a temporary one will be
1186     generated if not supplied)
1187
1188     @rtype: list
1189     @return: A (possibly empty) list of NewComment objects will be returned
1190
1191     """
1192
1193     privatetrans = False
1194     if session is None:
1195         session = DBConn().session()
1196         privatetrans = True
1197
1198     q = session.query(NewComment)
1199     if package is not None: q = q.filter_by(package=package)
1200     if version is not None: q = q.filter_by(version=version)
1201     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1202
1203     ret = q.all()
1204
1205     if privatetrans:
1206         session.close()
1207
1208     return ret
1209
1210 __all__.append('get_new_comments')
1211
1212 ################################################################################
1213
1214 class Override(object):
1215     def __init__(self, *args, **kwargs):
1216         pass
1217
1218     def __repr__(self):
1219         return '<Override %s (%s)>' % (self.package, self.suite_id)
1220
1221 __all__.append('Override')
1222
1223 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1224     """
1225     Returns Override object for the given parameters
1226
1227     @type package: string
1228     @param package: The name of the package
1229
1230     @type suite: string, list or None
1231     @param suite: The name of the suite (or suites if a list) to limit to.  If
1232                   None, don't limit.  Defaults to None.
1233
1234     @type component: string, list or None
1235     @param component: The name of the component (or components if a list) to
1236                       limit to.  If None, don't limit.  Defaults to None.
1237
1238     @type overridetype: string, list or None
1239     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1240                          limit to.  If None, don't limit.  Defaults to None.
1241
1242     @type session: Session
1243     @param session: Optional SQLA session object (a temporary one will be
1244     generated if not supplied)
1245
1246     @rtype: list
1247     @return: A (possibly empty) list of Override objects will be returned
1248
1249     """
1250     privatetrans = False
1251     if session is None:
1252         session = DBConn().session()
1253         privatetrans = True
1254
1255     q = session.query(Override)
1256     q = q.filter_by(package=package)
1257
1258     if suite is not None:
1259         if not isinstance(suite, list): suite = [suite]
1260         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1261
1262     if component is not None:
1263         if not isinstance(component, list): component = [component]
1264         q = q.join(Component).filter(Component.component_name.in_(component))
1265
1266     if overridetype is not None:
1267         if not isinstance(overridetype, list): overridetype = [overridetype]
1268         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1269
1270     ret = q.all()
1271
1272     if privatetrans:
1273         session.close()
1274
1275     return ret
1276
1277 __all__.append('get_override')
1278
1279
1280 ################################################################################
1281
1282 class OverrideType(object):
1283     def __init__(self, *args, **kwargs):
1284         pass
1285
1286     def __repr__(self):
1287         return '<OverrideType %s>' % self.overridetype
1288
1289 __all__.append('OverrideType')
1290
1291 def get_override_type(override_type, session=None):
1292     """
1293     Returns OverrideType object for given C{override type}.
1294
1295     @type override_type: string
1296     @param override_type: The name of the override type
1297
1298     @type session: Session
1299     @param session: Optional SQLA session object (a temporary one will be
1300     generated if not supplied)
1301
1302     @rtype: int
1303     @return: the database id for the given override type
1304
1305     """
1306     privatetrans = False
1307     if session is None:
1308         session = DBConn().session()
1309         privatetrans = True
1310
1311     q = session.query(OverrideType).filter_by(overridetype=override_type)
1312
1313     if q.count() == 0:
1314         ret = None
1315     else:
1316         ret = q.one()
1317
1318     if privatetrans:
1319         session.close()
1320
1321     return ret
1322
1323 __all__.append('get_override_type')
1324
1325 ################################################################################
1326
1327 class PendingContentAssociation(object):
1328     def __init__(self, *args, **kwargs):
1329         pass
1330
1331     def __repr__(self):
1332         return '<PendingContentAssociation %s>' % self.pca_id
1333
1334 __all__.append('PendingContentAssociation')
1335
1336 def insert_pending_content_paths(package, fullpaths, session=None):
1337     """
1338     Make sure given paths are temporarily associated with given
1339     package
1340
1341     @type package: dict
1342     @param package: the package to associate with should have been read in from the binary control file
1343     @type fullpaths: list
1344     @param fullpaths: the list of paths of the file being associated with the binary
1345     @type session: SQLAlchemy session
1346     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1347     is responsible for ensuring a transaction has begun and committing the
1348     results or rolling back based on the result code.  If not passed, a commit
1349     will be performed at the end of the function
1350
1351     @return: True upon success, False if there is a problem
1352     """
1353
1354     privatetrans = False
1355
1356     if session is None:
1357         session = DBConn().session()
1358         privatetrans = True
1359
1360     try:
1361         arch = get_architecture(package['Architecture'], session)
1362         arch_id = arch.arch_id
1363
1364         # Remove any already existing recorded files for this package
1365         q = session.query(PendingContentAssociation)
1366         q = q.filter_by(package=package['Package'])
1367         q = q.filter_by(version=package['Version'])
1368         q = q.filter_by(architecture=arch_id)
1369         q.delete()
1370
1371         # Insert paths
1372         pathcache = {}
1373         for fullpath in fullpaths:
1374             (path, file) = os.path.split(fullpath)
1375
1376             if path.startswith( "./" ):
1377                 path = path[2:]
1378
1379             filepath_id = get_or_set_contents_path_id(path, session)
1380             filename_id = get_or_set_contents_file_id(file, session)
1381
1382             pathcache[fullpath] = (filepath_id, filename_id)
1383
1384         for fullpath, dat in pathcache.items():
1385             pca = PendingContentAssociation()
1386             pca.package = package['Package']
1387             pca.version = package['Version']
1388             pca.filepath_id = dat[0]
1389             pca.filename_id = dat[1]
1390             pca.architecture = arch_id
1391             session.add(pca)
1392
1393         # Only commit if we set up the session ourself
1394         if privatetrans:
1395             session.commit()
1396             session.close()
1397         else:
1398             session.flush()
1399
1400         return True
1401     except Exception, e:
1402         traceback.print_exc()
1403
1404         # Only rollback if we set up the session ourself
1405         if privatetrans:
1406             session.rollback()
1407             session.close()
1408
1409         return False
1410
1411 __all__.append('insert_pending_content_paths')
1412
1413 ################################################################################
1414
1415 class Priority(object):
1416     def __init__(self, *args, **kwargs):
1417         pass
1418
1419     def __eq__(self, val):
1420         if isinstance(val, str):
1421             return (self.priority == val)
1422         # This signals to use the normal comparison operator
1423         return NotImplemented
1424
1425     def __ne__(self, val):
1426         if isinstance(val, str):
1427             return (self.priority != val)
1428         # This signals to use the normal comparison operator
1429         return NotImplemented
1430
1431     def __repr__(self):
1432         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1433
1434 __all__.append('Priority')
1435
1436 def get_priority(priority, session=None):
1437     """
1438     Returns Priority object for given C{priority name}.
1439
1440     @type priority: string
1441     @param priority: The name of the priority
1442
1443     @type session: Session
1444     @param session: Optional SQLA session object (a temporary one will be
1445     generated if not supplied)
1446
1447     @rtype: Priority
1448     @return: Priority object for the given priority
1449
1450     """
1451     privatetrans = False
1452     if session is None:
1453         session = DBConn().session()
1454         privatetrans = True
1455
1456     q = session.query(Priority).filter_by(priority=priority)
1457
1458     if q.count() == 0:
1459         ret = None
1460     else:
1461         ret = q.one()
1462
1463     if privatetrans:
1464         session.close()
1465
1466     return ret
1467
1468 __all__.append('get_priority')
1469
1470 def get_priorities(session=None):
1471     """
1472     Returns dictionary of priority names -> id mappings
1473
1474     @type session: Session
1475     @param session: Optional SQL session object (a temporary one will be
1476     generated if not supplied)
1477
1478     @rtype: dictionary
1479     @return: dictionary of priority names -> id mappings
1480     """
1481     privatetrans = False
1482     if session is None:
1483         session = DBConn().session()
1484         privatetrans = True
1485
1486     ret = {}
1487     q = session.query(Priority)
1488     for x in q.all():
1489         ret[x.priority] = x.priority_id
1490
1491     if privatetrans:
1492         session.close()
1493
1494     return ret
1495
1496 __all__.append('get_priorities')
1497
1498 ################################################################################
1499
1500 class Queue(object):
1501     def __init__(self, *args, **kwargs):
1502         pass
1503
1504     def __repr__(self):
1505         return '<Queue %s>' % self.queue_name
1506
1507     def autobuild_upload(self, changes, srcpath, session=None):
1508         """
1509         Update queue_build database table used for incoming autobuild support.
1510
1511         @type changes: Changes
1512         @param changes: changes object for the upload to process
1513
1514         @type srcpath: string
1515         @param srcpath: path for the queue file entries/link destinations
1516
1517         @type session: SQLAlchemy session
1518         @param session: Optional SQLAlchemy session.  If this is passed, the
1519         caller is responsible for ensuring a transaction has begun and
1520         committing the results or rolling back based on the result code.  If
1521         not passed, a commit will be performed at the end of the function,
1522         otherwise the caller is responsible for commiting.
1523
1524         @rtype: NoneType or string
1525         @return: None if the operation failed, a string describing the error if not
1526         """
1527
1528         privatetrans = False
1529         if session is None:
1530             session = DBConn().session()
1531             privatetrans = True
1532
1533         # TODO: Remove by moving queue config into the database
1534         conf = Config()
1535
1536         for suitename in changes.changes["distribution"].keys():
1537             # TODO: Move into database as:
1538             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1539             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1540             #       This also gets rid of the SecurityQueueBuild hack below
1541             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1542                 continue
1543
1544             # Find suite object
1545             s = get_suite(suitename, session)
1546             if s is None:
1547                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1548
1549             # TODO: Get from database as above
1550             dest_dir = conf["Dir::QueueBuild"]
1551
1552             # TODO: Move into database as above
1553             if conf.FindB("Dinstall::SecurityQueueBuild"):
1554                 dest_dir = os.path.join(dest_dir, suitename)
1555
1556             for file_entry in changes.files.keys():
1557                 src = os.path.join(srcpath, file_entry)
1558                 dest = os.path.join(dest_dir, file_entry)
1559
1560                 # TODO: Move into database as above
1561                 if conf.FindB("Dinstall::SecurityQueueBuild"):
1562                     # Copy it since the original won't be readable by www-data
1563                     utils.copy(src, dest)
1564                 else:
1565                     # Create a symlink to it
1566                     os.symlink(src, dest)
1567
1568                 qb = QueueBuild()
1569                 qb.suite_id = s.suite_id
1570                 qb.queue_id = self.queue_id
1571                 qb.filename = dest
1572                 qb.in_queue = True
1573
1574                 session.add(qb)
1575
1576             # If the .orig.tar.gz is in the pool, create a symlink to
1577             # it (if one doesn't already exist)
1578             if changes.orig_tar_id:
1579                 # Determine the .orig.tar.gz file name
1580                 for dsc_file in changes.dsc_files.keys():
1581                     if dsc_file.endswith(".orig.tar.gz"):
1582                         filename = dsc_file
1583
1584                 dest = os.path.join(dest_dir, filename)
1585
1586                 # If it doesn't exist, create a symlink
1587                 if not os.path.exists(dest):
1588                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1589                                         {'id': changes.orig_tar_id})
1590                     res = q.fetchone()
1591                     if not res:
1592                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1593
1594                     src = os.path.join(res[0], res[1])
1595                     os.symlink(src, dest)
1596
1597                     # Add it to the list of packages for later processing by apt-ftparchive
1598                     qb = QueueBuild()
1599                     qb.suite_id = s.suite_id
1600                     qb.queue_id = self.queue_id
1601                     qb.filename = dest
1602                     qb.in_queue = True
1603                     session.add(qb)
1604
1605                 # If it does, update things to ensure it's not removed prematurely
1606                 else:
1607                     qb = get_queue_build(dest, s.suite_id, session)
1608                     if qb is None:
1609                         qb.in_queue = True
1610                         qb.last_used = None
1611                         session.add(qb)
1612
1613         if privatetrans:
1614             session.commit()
1615             session.close()
1616
1617         return None
1618
1619 __all__.append('Queue')
1620
1621 def get_queue(queuename, session=None):
1622     """
1623     Returns Queue object for given C{queue name}.
1624
1625     @type queuename: string
1626     @param queuename: The name of the queue
1627
1628     @type session: Session
1629     @param session: Optional SQLA session object (a temporary one will be
1630     generated if not supplied)
1631
1632     @rtype: Queue
1633     @return: Queue object for the given queue
1634
1635     """
1636     privatetrans = False
1637     if session is None:
1638         session = DBConn().session()
1639         privatetrans = True
1640
1641     q = session.query(Queue).filter_by(queue_name=queuename)
1642     if q.count() == 0:
1643         ret = None
1644     else:
1645         ret = q.one()
1646
1647     if privatetrans:
1648         session.close()
1649
1650     return ret
1651
1652 __all__.append('get_queue')
1653
1654 ################################################################################
1655
1656 class QueueBuild(object):
1657     def __init__(self, *args, **kwargs):
1658         pass
1659
1660     def __repr__(self):
1661         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1662
1663 __all__.append('QueueBuild')
1664
1665 def get_queue_build(filename, suite, session=None):
1666     """
1667     Returns QueueBuild object for given C{filename} and C{suite}.
1668
1669     @type filename: string
1670     @param filename: The name of the file
1671
1672     @type suiteid: int or str
1673     @param suiteid: Suite name or ID
1674
1675     @type session: Session
1676     @param session: Optional SQLA session object (a temporary one will be
1677     generated if not supplied)
1678
1679     @rtype: Queue
1680     @return: Queue object for the given queue
1681
1682     """
1683     privatetrans = False
1684     if session is None:
1685         session = DBConn().session()
1686         privatetrans = True
1687
1688     if isinstance(suite, int):
1689         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1690     else:
1691         q = session.query(QueueBuild).filter_by(filename=filename)
1692         q = q.join(Suite).filter_by(suite_name=suite)
1693
1694     if q.count() == 0:
1695         ret = None
1696     else:
1697         ret = q.one()
1698
1699     if privatetrans:
1700         session.close()
1701
1702     return ret
1703
1704 __all__.append('get_queue_build')
1705
1706 ################################################################################
1707
1708 class Section(object):
1709     def __init__(self, *args, **kwargs):
1710         pass
1711
1712     def __eq__(self, val):
1713         if isinstance(val, str):
1714             return (self.section == val)
1715         # This signals to use the normal comparison operator
1716         return NotImplemented
1717
1718     def __ne__(self, val):
1719         if isinstance(val, str):
1720             return (self.section != val)
1721         # This signals to use the normal comparison operator
1722         return NotImplemented
1723
1724     def __repr__(self):
1725         return '<Section %s>' % self.section
1726
1727 __all__.append('Section')
1728
1729 def get_section(section, session=None):
1730     """
1731     Returns Section object for given C{section name}.
1732
1733     @type section: string
1734     @param section: The name of the section
1735
1736     @type session: Session
1737     @param session: Optional SQLA session object (a temporary one will be
1738     generated if not supplied)
1739
1740     @rtype: Section
1741     @return: Section object for the given section name
1742
1743     """
1744     privatetrans = False
1745     if session is None:
1746         session = DBConn().session()
1747         privatetrans = True
1748
1749     q = session.query(Section).filter_by(section=section)
1750     if q.count() == 0:
1751         ret = None
1752     else:
1753         ret = q.one()
1754
1755     if privatetrans:
1756         session.close()
1757
1758     return ret
1759
1760 __all__.append('get_section')
1761
1762 def get_sections(session=None):
1763     """
1764     Returns dictionary of section names -> id mappings
1765
1766     @type session: Session
1767     @param session: Optional SQL session object (a temporary one will be
1768     generated if not supplied)
1769
1770     @rtype: dictionary
1771     @return: dictionary of section names -> id mappings
1772     """
1773     privatetrans = False
1774     if session is None:
1775         session = DBConn().session()
1776         privatetrans = True
1777
1778     ret = {}
1779     q = session.query(Section)
1780     for x in q.all():
1781         ret[x.section] = x.section_id
1782
1783     if privatetrans:
1784         session.close()
1785
1786     return ret
1787
1788 __all__.append('get_sections')
1789
1790 ################################################################################
1791
1792 class DBSource(object):
1793     def __init__(self, *args, **kwargs):
1794         pass
1795
1796     def __repr__(self):
1797         return '<DBSource %s (%s)>' % (self.source, self.version)
1798
1799 __all__.append('DBSource')
1800
1801 def source_exists(source, source_version, suites = ["any"], session=None):
1802     """
1803     Ensure that source exists somewhere in the archive for the binary
1804     upload being processed.
1805       1. exact match     => 1.0-3
1806       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1807
1808     @type package: string
1809     @param package: package source name
1810
1811     @type source_version: string
1812     @param source_version: expected source version
1813
1814     @type suites: list
1815     @param suites: list of suites to check in, default I{any}
1816
1817     @type session: Session
1818     @param session: Optional SQLA session object (a temporary one will be
1819     generated if not supplied)
1820
1821     @rtype: int
1822     @return: returns 1 if a source with expected version is found, otherwise 0
1823
1824     """
1825
1826     privatetrans = False
1827     if session is None:
1828         session = DBConn().session()
1829         privatetrans = True
1830
1831     cnf = Config()
1832     ret = 1
1833
1834     for suite in suites:
1835         q = session.query(DBSource).filter_by(source=source)
1836         if suite != "any":
1837             # source must exist in suite X, or in some other suite that's
1838             # mapped to X, recursively... silent-maps are counted too,
1839             # unreleased-maps aren't.
1840             maps = cnf.ValueList("SuiteMappings")[:]
1841             maps.reverse()
1842             maps = [ m.split() for m in maps ]
1843             maps = [ (x[1], x[2]) for x in maps
1844                             if x[0] == "map" or x[0] == "silent-map" ]
1845             s = [suite]
1846             for x in maps:
1847                 if x[1] in s and x[0] not in s:
1848                     s.append(x[0])
1849
1850             q = q.join(SrcAssociation).join(Suite)
1851             q = q.filter(Suite.suite_name.in_(s))
1852
1853         # Reduce the query results to a list of version numbers
1854         ql = [ j.version for j in q.all() ]
1855
1856         # Try (1)
1857         if source_version in ql:
1858             continue
1859
1860         # Try (2)
1861         from daklib.regexes import re_bin_only_nmu
1862         orig_source_version = re_bin_only_nmu.sub('', source_version)
1863         if orig_source_version in ql:
1864             continue
1865
1866         # No source found so return not ok
1867         ret = 0
1868
1869     if privatetrans:
1870         session.close()
1871
1872     return ret
1873
1874 __all__.append('source_exists')
1875
1876 def get_suites_source_in(source, session=None):
1877     """
1878     Returns list of Suite objects which given C{source} name is in
1879
1880     @type source: str
1881     @param source: DBSource package name to search for
1882
1883     @rtype: list
1884     @return: list of Suite objects for the given source
1885     """
1886
1887     privatetrans = False
1888     if session is None:
1889         session = DBConn().session()
1890         privatetrans = True
1891
1892     ret = session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1893
1894     if privatetrans:
1895         session.close()
1896
1897     return ret
1898
1899 __all__.append('get_suites_source_in')
1900
1901 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1902     """
1903     Returns list of DBSource objects for given C{source} name and other parameters
1904
1905     @type source: str
1906     @param source: DBSource package name to search for
1907
1908     @type source: str or None
1909     @param source: DBSource version name to search for or None if not applicable
1910
1911     @type dm_upload_allowed: bool
1912     @param dm_upload_allowed: If None, no effect.  If True or False, only
1913     return packages with that dm_upload_allowed setting
1914
1915     @type session: Session
1916     @param session: Optional SQL session object (a temporary one will be
1917     generated if not supplied)
1918
1919     @rtype: list
1920     @return: list of DBSource objects for the given name (may be empty)
1921     """
1922     privatetrans = False
1923     if session is None:
1924         session = DBConn().session()
1925         privatetrans = True
1926
1927     q = session.query(DBSource).filter_by(source=source)
1928
1929     if version is not None:
1930         q = q.filter_by(version=version)
1931
1932     if dm_upload_allowed is not None:
1933         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1934
1935     ret = q.all()
1936
1937     if privatetrans:
1938         session.close()
1939
1940     return ret
1941
1942 __all__.append('get_sources_from_name')
1943
1944 def get_source_in_suite(source, suite, session=None):
1945     """
1946     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1947
1948       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1949       - B{suite} - a suite name, eg. I{unstable}
1950
1951     @type source: string
1952     @param source: source package name
1953
1954     @type suite: string
1955     @param suite: the suite name
1956
1957     @rtype: string
1958     @return: the version for I{source} in I{suite}
1959
1960     """
1961     privatetrans = False
1962     if session is None:
1963         session = DBConn().session()
1964         privatetrans = True
1965
1966     q = session.query(SrcAssociation)
1967     q = q.join('source').filter_by(source=source)
1968     q = q.join('suite').filter_by(suite_name=suite)
1969
1970     if q.count() == 0:
1971         ret =  None
1972     else:
1973         # ???: Maybe we should just return the SrcAssociation object instead
1974         ret = q.one().source
1975
1976     if privatetrans:
1977         session.close()
1978
1979     return ret
1980
1981 __all__.append('get_source_in_suite')
1982
1983 ################################################################################
1984
1985 class SrcAssociation(object):
1986     def __init__(self, *args, **kwargs):
1987         pass
1988
1989     def __repr__(self):
1990         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1991
1992 __all__.append('SrcAssociation')
1993
1994 ################################################################################
1995
1996 class SrcUploader(object):
1997     def __init__(self, *args, **kwargs):
1998         pass
1999
2000     def __repr__(self):
2001         return '<SrcUploader %s>' % self.uploader_id
2002
2003 __all__.append('SrcUploader')
2004
2005 ################################################################################
2006
2007 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2008                  ('SuiteID', 'suite_id'),
2009                  ('Version', 'version'),
2010                  ('Origin', 'origin'),
2011                  ('Label', 'label'),
2012                  ('Description', 'description'),
2013                  ('Untouchable', 'untouchable'),
2014                  ('Announce', 'announce'),
2015                  ('Codename', 'codename'),
2016                  ('OverrideCodename', 'overridecodename'),
2017                  ('ValidTime', 'validtime'),
2018                  ('Priority', 'priority'),
2019                  ('NotAutomatic', 'notautomatic'),
2020                  ('CopyChanges', 'copychanges'),
2021                  ('CopyDotDak', 'copydotdak'),
2022                  ('CommentsDir', 'commentsdir'),
2023                  ('OverrideSuite', 'overridesuite'),
2024                  ('ChangelogBase', 'changelogbase')]
2025
2026
2027 class Suite(object):
2028     def __init__(self, *args, **kwargs):
2029         pass
2030
2031     def __repr__(self):
2032         return '<Suite %s>' % self.suite_name
2033
2034     def __eq__(self, val):
2035         if isinstance(val, str):
2036             return (self.suite_name == val)
2037         # This signals to use the normal comparison operator
2038         return NotImplemented
2039
2040     def __ne__(self, val):
2041         if isinstance(val, str):
2042             return (self.suite_name != val)
2043         # This signals to use the normal comparison operator
2044         return NotImplemented
2045
2046     def details(self):
2047         ret = []
2048         for disp, field in SUITE_FIELDS:
2049             val = getattr(self, field, None)
2050             if val is not None:
2051                 ret.append("%s: %s" % (disp, val))
2052
2053         return "\n".join(ret)
2054
2055 __all__.append('Suite')
2056
2057 def get_suite_architecture(suite, architecture, session=None):
2058     """
2059     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2060     doesn't exist
2061
2062     @type suite: str
2063     @param suite: Suite name to search for
2064
2065     @type architecture: str
2066     @param architecture: Architecture name to search for
2067
2068     @type session: Session
2069     @param session: Optional SQL session object (a temporary one will be
2070     generated if not supplied)
2071
2072     @rtype: SuiteArchitecture
2073     @return: the SuiteArchitecture object or None
2074     """
2075
2076     privatetrans = False
2077     if session is None:
2078         session = DBConn().session()
2079         privatetrans = True
2080
2081     q = session.query(SuiteArchitecture)
2082     q = q.join(Architecture).filter_by(arch_string=architecture)
2083     q = q.join(Suite).filter_by(suite_name=suite)
2084
2085     if q.count() == 0:
2086         ret = None
2087     else:
2088         ret = q.one()
2089
2090     if privatetrans:
2091         session.close()
2092
2093     return ret
2094
2095 __all__.append('get_suite_architecture')
2096
2097 def get_suite(suite, session=None):
2098     """
2099     Returns Suite object for given C{suite name}.
2100
2101     @type suite: string
2102     @param suite: The name of the suite
2103
2104     @type session: Session
2105     @param session: Optional SQLA session object (a temporary one will be
2106     generated if not supplied)
2107
2108     @rtype: Suite
2109     @return: Suite object for the requested suite name (None if not presenT)
2110
2111     """
2112     privatetrans = False
2113     if session is None:
2114         session = DBConn().session()
2115         privatetrans = True
2116
2117     q = session.query(Suite).filter_by(suite_name=suite)
2118
2119     if q.count() == 0:
2120         ret = None
2121     else:
2122         ret = q.one()
2123
2124     if privatetrans:
2125         session.close()
2126
2127     return ret
2128
2129 __all__.append('get_suite')
2130
2131 ################################################################################
2132
2133 class SuiteArchitecture(object):
2134     def __init__(self, *args, **kwargs):
2135         pass
2136
2137     def __repr__(self):
2138         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2139
2140 __all__.append('SuiteArchitecture')
2141
2142 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2143     """
2144     Returns list of Architecture objects for given C{suite} name
2145
2146     @type source: str
2147     @param source: Suite name to search for
2148
2149     @type skipsrc: boolean
2150     @param skipsrc: Whether to skip returning the 'source' architecture entry
2151     (Default False)
2152
2153     @type skipall: boolean
2154     @param skipall: Whether to skip returning the 'all' architecture entry
2155     (Default False)
2156
2157     @type session: Session
2158     @param session: Optional SQL session object (a temporary one will be
2159     generated if not supplied)
2160
2161     @rtype: list
2162     @return: list of Architecture objects for the given name (may be empty)
2163     """
2164
2165     privatetrans = False
2166     if session is None:
2167         session = DBConn().session()
2168         privatetrans = True
2169
2170     q = session.query(Architecture)
2171     q = q.join(SuiteArchitecture)
2172     q = q.join(Suite).filter_by(suite_name=suite)
2173
2174     if skipsrc:
2175         q = q.filter(Architecture.arch_string != 'source')
2176
2177     if skipall:
2178         q = q.filter(Architecture.arch_string != 'all')
2179
2180     q = q.order_by('arch_string')
2181
2182     ret = q.all()
2183
2184     if privatetrans:
2185         session.close()
2186
2187     return ret
2188
2189 __all__.append('get_suite_architectures')
2190
2191 ################################################################################
2192
2193 class Uid(object):
2194     def __init__(self, *args, **kwargs):
2195         pass
2196
2197     def __eq__(self, val):
2198         if isinstance(val, str):
2199             return (self.uid == val)
2200         # This signals to use the normal comparison operator
2201         return NotImplemented
2202
2203     def __ne__(self, val):
2204         if isinstance(val, str):
2205             return (self.uid != val)
2206         # This signals to use the normal comparison operator
2207         return NotImplemented
2208
2209     def __repr__(self):
2210         return '<Uid %s (%s)>' % (self.uid, self.name)
2211
2212 __all__.append('Uid')
2213
2214 def add_database_user(uidname, session=None):
2215     """
2216     Adds a database user
2217
2218     @type uidname: string
2219     @param uidname: The uid of the user to add
2220
2221     @type session: SQLAlchemy
2222     @param session: Optional SQL session object (a temporary one will be
2223     generated if not supplied).  If not passed, a commit will be performed at
2224     the end of the function, otherwise the caller is responsible for commiting.
2225
2226     @rtype: Uid
2227     @return: the uid object for the given uidname
2228     """
2229
2230     privatetrans = False
2231     if session is None:
2232         session = DBConn().session()
2233         privatetrans = True
2234
2235     session.execute("CREATE USER :uid", {'uid': uidname})
2236
2237     if privatetrans:
2238         session.commit()
2239         session.close()
2240
2241 __all__.append('add_database_user')
2242
2243 def get_or_set_uid(uidname, session=None):
2244     """
2245     Returns uid object for given uidname.
2246
2247     If no matching uidname is found, a row is inserted.
2248
2249     @type uidname: string
2250     @param uidname: The uid to add
2251
2252     @type session: SQLAlchemy
2253     @param session: Optional SQL session object (a temporary one will be
2254     generated if not supplied).  If not passed, a commit will be performed at
2255     the end of the function, otherwise the caller is responsible for commiting.
2256
2257     @rtype: Uid
2258     @return: the uid object for the given uidname
2259     """
2260
2261     privatetrans = False
2262     if session is None:
2263         session = DBConn().session()
2264         privatetrans = True
2265
2266     q = session.query(Uid).filter_by(uid=uidname)
2267
2268     if q.count() < 1:
2269         uid = Uid()
2270         uid.uid = uidname
2271         session.add(uid)
2272         if privatetrans:
2273             session.commit()
2274         else:
2275             session.flush()
2276         ret = uid
2277     else:
2278         ret = q.one()
2279
2280     if privatetrans:
2281         session.close()
2282
2283     return ret
2284
2285 __all__.append('get_or_set_uid')
2286
2287
2288 def get_uid_from_fingerprint(fpr, session=None):
2289     privatetrans = False
2290     if session is None:
2291         session = DBConn().session()
2292         privatetrans = True
2293
2294     q = session.query(Uid)
2295     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2296
2297     if q.count() != 1:
2298         ret = None
2299     else:
2300         ret = q.one()
2301
2302     if privatetrans:
2303         session.close()
2304
2305     return ret
2306
2307 __all__.append('get_uid_from_fingerprint')
2308
2309 ################################################################################
2310
2311 class DBConn(Singleton):
2312     """
2313     database module init.
2314     """
2315     def __init__(self, *args, **kwargs):
2316         super(DBConn, self).__init__(*args, **kwargs)
2317
2318     def _startup(self, *args, **kwargs):
2319         self.debug = False
2320         if kwargs.has_key('debug'):
2321             self.debug = True
2322         self.__createconn()
2323
2324     def __setuptables(self):
2325         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2326         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2327         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2328         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2329         self.tbl_component = Table('component', self.db_meta, autoload=True)
2330         self.tbl_config = Table('config', self.db_meta, autoload=True)
2331         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2332         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2333         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2334         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2335         self.tbl_files = Table('files', self.db_meta, autoload=True)
2336         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2337         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2338         self.tbl_location = Table('location', self.db_meta, autoload=True)
2339         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2340         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2341         self.tbl_override = Table('override', self.db_meta, autoload=True)
2342         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2343         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2344         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2345         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2346         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2347         self.tbl_section = Table('section', self.db_meta, autoload=True)
2348         self.tbl_source = Table('source', self.db_meta, autoload=True)
2349         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2350         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2351         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2352         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2353         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2354
2355     def __setupmappers(self):
2356         mapper(Architecture, self.tbl_architecture,
2357                properties = dict(arch_id = self.tbl_architecture.c.id))
2358
2359         mapper(Archive, self.tbl_archive,
2360                properties = dict(archive_id = self.tbl_archive.c.id,
2361                                  archive_name = self.tbl_archive.c.name))
2362
2363         mapper(BinAssociation, self.tbl_bin_associations,
2364                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2365                                  suite_id = self.tbl_bin_associations.c.suite,
2366                                  suite = relation(Suite),
2367                                  binary_id = self.tbl_bin_associations.c.bin,
2368                                  binary = relation(DBBinary)))
2369
2370         mapper(DBBinary, self.tbl_binaries,
2371                properties = dict(binary_id = self.tbl_binaries.c.id,
2372                                  package = self.tbl_binaries.c.package,
2373                                  version = self.tbl_binaries.c.version,
2374                                  maintainer_id = self.tbl_binaries.c.maintainer,
2375                                  maintainer = relation(Maintainer),
2376                                  source_id = self.tbl_binaries.c.source,
2377                                  source = relation(DBSource),
2378                                  arch_id = self.tbl_binaries.c.architecture,
2379                                  architecture = relation(Architecture),
2380                                  poolfile_id = self.tbl_binaries.c.file,
2381                                  poolfile = relation(PoolFile),
2382                                  binarytype = self.tbl_binaries.c.type,
2383                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2384                                  fingerprint = relation(Fingerprint),
2385                                  install_date = self.tbl_binaries.c.install_date,
2386                                  binassociations = relation(BinAssociation,
2387                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2388
2389         mapper(Component, self.tbl_component,
2390                properties = dict(component_id = self.tbl_component.c.id,
2391                                  component_name = self.tbl_component.c.name))
2392
2393         mapper(DBConfig, self.tbl_config,
2394                properties = dict(config_id = self.tbl_config.c.id))
2395
2396         mapper(ContentAssociation, self.tbl_content_associations,
2397                properties = dict(ca_id = self.tbl_content_associations.c.id,
2398                                  filename_id = self.tbl_content_associations.c.filename,
2399                                  filename    = relation(ContentFilename),
2400                                  filepath_id = self.tbl_content_associations.c.filepath,
2401                                  filepath    = relation(ContentFilepath),
2402                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
2403                                  binary      = relation(DBBinary)))
2404
2405
2406         mapper(ContentFilename, self.tbl_content_file_names,
2407                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2408                                  filename = self.tbl_content_file_names.c.file))
2409
2410         mapper(ContentFilepath, self.tbl_content_file_paths,
2411                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2412                                  filepath = self.tbl_content_file_paths.c.path))
2413
2414         mapper(DSCFile, self.tbl_dsc_files,
2415                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2416                                  source_id = self.tbl_dsc_files.c.source,
2417                                  source = relation(DBSource),
2418                                  poolfile_id = self.tbl_dsc_files.c.file,
2419                                  poolfile = relation(PoolFile)))
2420
2421         mapper(PoolFile, self.tbl_files,
2422                properties = dict(file_id = self.tbl_files.c.id,
2423                                  filesize = self.tbl_files.c.size,
2424                                  location_id = self.tbl_files.c.location,
2425                                  location = relation(Location)))
2426
2427         mapper(Fingerprint, self.tbl_fingerprint,
2428                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2429                                  uid_id = self.tbl_fingerprint.c.uid,
2430                                  uid = relation(Uid),
2431                                  keyring_id = self.tbl_fingerprint.c.keyring,
2432                                  keyring = relation(Keyring)))
2433
2434         mapper(Keyring, self.tbl_keyrings,
2435                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2436                                  keyring_id = self.tbl_keyrings.c.id))
2437
2438         mapper(Location, self.tbl_location,
2439                properties = dict(location_id = self.tbl_location.c.id,
2440                                  component_id = self.tbl_location.c.component,
2441                                  component = relation(Component),
2442                                  archive_id = self.tbl_location.c.archive,
2443                                  archive = relation(Archive),
2444                                  archive_type = self.tbl_location.c.type))
2445
2446         mapper(Maintainer, self.tbl_maintainer,
2447                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2448
2449         mapper(NewComment, self.tbl_new_comments,
2450                properties = dict(comment_id = self.tbl_new_comments.c.id))
2451
2452         mapper(Override, self.tbl_override,
2453                properties = dict(suite_id = self.tbl_override.c.suite,
2454                                  suite = relation(Suite),
2455                                  component_id = self.tbl_override.c.component,
2456                                  component = relation(Component),
2457                                  priority_id = self.tbl_override.c.priority,
2458                                  priority = relation(Priority),
2459                                  section_id = self.tbl_override.c.section,
2460                                  section = relation(Section),
2461                                  overridetype_id = self.tbl_override.c.type,
2462                                  overridetype = relation(OverrideType)))
2463
2464         mapper(OverrideType, self.tbl_override_type,
2465                properties = dict(overridetype = self.tbl_override_type.c.type,
2466                                  overridetype_id = self.tbl_override_type.c.id))
2467
2468         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2469                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2470                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
2471                                  filepath = relation(ContentFilepath),
2472                                  filename_id = self.tbl_pending_content_associations.c.filename,
2473                                  filename = relation(ContentFilename)))
2474
2475         mapper(Priority, self.tbl_priority,
2476                properties = dict(priority_id = self.tbl_priority.c.id))
2477
2478         mapper(Queue, self.tbl_queue,
2479                properties = dict(queue_id = self.tbl_queue.c.id))
2480
2481         mapper(QueueBuild, self.tbl_queue_build,
2482                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2483                                  queue_id = self.tbl_queue_build.c.queue,
2484                                  queue = relation(Queue, backref='queuebuild')))
2485
2486         mapper(Section, self.tbl_section,
2487                properties = dict(section_id = self.tbl_section.c.id))
2488
2489         mapper(DBSource, self.tbl_source,
2490                properties = dict(source_id = self.tbl_source.c.id,
2491                                  version = self.tbl_source.c.version,
2492                                  maintainer_id = self.tbl_source.c.maintainer,
2493                                  maintainer = relation(Maintainer,
2494                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2495                                  poolfile_id = self.tbl_source.c.file,
2496                                  poolfile = relation(PoolFile),
2497                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2498                                  fingerprint = relation(Fingerprint),
2499                                  changedby_id = self.tbl_source.c.changedby,
2500                                  changedby = relation(Maintainer,
2501                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2502                                  srcfiles = relation(DSCFile,
2503                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2504                                  srcassociations = relation(SrcAssociation,
2505                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2506
2507         mapper(SrcAssociation, self.tbl_src_associations,
2508                properties = dict(sa_id = self.tbl_src_associations.c.id,
2509                                  suite_id = self.tbl_src_associations.c.suite,
2510                                  suite = relation(Suite),
2511                                  source_id = self.tbl_src_associations.c.source,
2512                                  source = relation(DBSource)))
2513
2514         mapper(SrcUploader, self.tbl_src_uploaders,
2515                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2516                                  source_id = self.tbl_src_uploaders.c.source,
2517                                  source = relation(DBSource,
2518                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2519                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2520                                  maintainer = relation(Maintainer,
2521                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2522
2523         mapper(Suite, self.tbl_suite,
2524                properties = dict(suite_id = self.tbl_suite.c.id))
2525
2526         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2527                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2528                                  suite = relation(Suite, backref='suitearchitectures'),
2529                                  arch_id = self.tbl_suite_architectures.c.architecture,
2530                                  architecture = relation(Architecture)))
2531
2532         mapper(Uid, self.tbl_uid,
2533                properties = dict(uid_id = self.tbl_uid.c.id,
2534                                  fingerprint = relation(Fingerprint)))
2535
2536     ## Connection functions
2537     def __createconn(self):
2538         from config import Config
2539         cnf = Config()
2540         if cnf["DB::Host"]:
2541             # TCP/IP
2542             connstr = "postgres://%s" % cnf["DB::Host"]
2543             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2544                 connstr += ":%s" % cnf["DB::Port"]
2545             connstr += "/%s" % cnf["DB::Name"]
2546         else:
2547             # Unix Socket
2548             connstr = "postgres:///%s" % cnf["DB::Name"]
2549             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2550                 connstr += "?port=%s" % cnf["DB::Port"]
2551
2552         self.db_pg   = create_engine(connstr, echo=self.debug)
2553         self.db_meta = MetaData()
2554         self.db_meta.bind = self.db_pg
2555         self.db_smaker = sessionmaker(bind=self.db_pg,
2556                                       autoflush=True,
2557                                       autocommit=False)
2558
2559         self.__setuptables()
2560         self.__setupmappers()
2561
2562     def session(self):
2563         return self.db_smaker()
2564
2565 __all__.append('DBConn')
2566
2567