]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
Merge commit 'lamby/master' into merge
[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=True):
1099     """
1100     Return the name of the maintainer behind C{maintainer_id}.
1101
1102     @type maintainer_id: int
1103     @param maintainer_id: the id of the maintainer
1104
1105     @rtype: string
1106     @return: the name of the maintainer
1107     """
1108
1109     privatetrans = False
1110     if session is None:
1111         session = DBConn().session()
1112         privatetrans = True
1113
1114     try:
1115         return session.query(Maintainer).get(maintainer_id).name
1116     finally:
1117         if privatetrans:
1118             session.close()
1119
1120 __all__.append('get_maintainer')
1121
1122 ################################################################################
1123
1124 class NewComment(object):
1125     def __init__(self, *args, **kwargs):
1126         pass
1127
1128     def __repr__(self):
1129         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1130
1131 __all__.append('NewComment')
1132
1133 def has_new_comment(package, version, session=None):
1134     """
1135     Returns true if the given combination of C{package}, C{version} has a comment.
1136
1137     @type package: string
1138     @param package: name of the package
1139
1140     @type version: string
1141     @param version: package version
1142
1143     @type session: Session
1144     @param session: Optional SQLA session object (a temporary one will be
1145     generated if not supplied)
1146
1147     @rtype: boolean
1148     @return: true/false
1149     """
1150
1151     privatetrans = False
1152     if session is None:
1153         session = DBConn().session()
1154         privatetrans = True
1155
1156     q = session.query(NewComment)
1157     q = q.filter_by(package=package)
1158     q = q.filter_by(version=version)
1159
1160     ret = q.count() > 0
1161
1162     if privatetrans:
1163         session.close()
1164
1165     return ret
1166
1167 __all__.append('has_new_comment')
1168
1169 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1170     """
1171     Returns (possibly empty) list of NewComment objects for the given
1172     parameters
1173
1174     @type package: string (optional)
1175     @param package: name of the package
1176
1177     @type version: string (optional)
1178     @param version: package version
1179
1180     @type comment_id: int (optional)
1181     @param comment_id: An id of a comment
1182
1183     @type session: Session
1184     @param session: Optional SQLA session object (a temporary one will be
1185     generated if not supplied)
1186
1187     @rtype: list
1188     @return: A (possibly empty) list of NewComment objects will be returned
1189
1190     """
1191
1192     privatetrans = False
1193     if session is None:
1194         session = DBConn().session()
1195         privatetrans = True
1196
1197     q = session.query(NewComment)
1198     if package is not None: q = q.filter_by(package=package)
1199     if version is not None: q = q.filter_by(version=version)
1200     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1201
1202     ret = q.all()
1203
1204     if privatetrans:
1205         session.close()
1206
1207     return ret
1208
1209 __all__.append('get_new_comments')
1210
1211 ################################################################################
1212
1213 class Override(object):
1214     def __init__(self, *args, **kwargs):
1215         pass
1216
1217     def __repr__(self):
1218         return '<Override %s (%s)>' % (self.package, self.suite_id)
1219
1220 __all__.append('Override')
1221
1222 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1223     """
1224     Returns Override object for the given parameters
1225
1226     @type package: string
1227     @param package: The name of the package
1228
1229     @type suite: string, list or None
1230     @param suite: The name of the suite (or suites if a list) to limit to.  If
1231                   None, don't limit.  Defaults to None.
1232
1233     @type component: string, list or None
1234     @param component: The name of the component (or components if a list) to
1235                       limit to.  If None, don't limit.  Defaults to None.
1236
1237     @type overridetype: string, list or None
1238     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1239                          limit to.  If None, don't limit.  Defaults to None.
1240
1241     @type session: Session
1242     @param session: Optional SQLA session object (a temporary one will be
1243     generated if not supplied)
1244
1245     @rtype: list
1246     @return: A (possibly empty) list of Override objects will be returned
1247
1248     """
1249     privatetrans = False
1250     if session is None:
1251         session = DBConn().session()
1252         privatetrans = True
1253
1254     q = session.query(Override)
1255     q = q.filter_by(package=package)
1256
1257     if suite is not None:
1258         if not isinstance(suite, list): suite = [suite]
1259         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1260
1261     if component is not None:
1262         if not isinstance(component, list): component = [component]
1263         q = q.join(Component).filter(Component.component_name.in_(component))
1264
1265     if overridetype is not None:
1266         if not isinstance(overridetype, list): overridetype = [overridetype]
1267         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1268
1269     ret = q.all()
1270
1271     if privatetrans:
1272         session.close()
1273
1274     return ret
1275
1276 __all__.append('get_override')
1277
1278
1279 ################################################################################
1280
1281 class OverrideType(object):
1282     def __init__(self, *args, **kwargs):
1283         pass
1284
1285     def __repr__(self):
1286         return '<OverrideType %s>' % self.overridetype
1287
1288 __all__.append('OverrideType')
1289
1290 def get_override_type(override_type, session=None):
1291     """
1292     Returns OverrideType object for given C{override type}.
1293
1294     @type override_type: string
1295     @param override_type: The name of the override type
1296
1297     @type session: Session
1298     @param session: Optional SQLA session object (a temporary one will be
1299     generated if not supplied)
1300
1301     @rtype: int
1302     @return: the database id for the given override type
1303
1304     """
1305     privatetrans = False
1306     if session is None:
1307         session = DBConn().session()
1308         privatetrans = True
1309
1310     q = session.query(OverrideType).filter_by(overridetype=override_type)
1311
1312     if q.count() == 0:
1313         ret = None
1314     else:
1315         ret = q.one()
1316
1317     if privatetrans:
1318         session.close()
1319
1320     return ret
1321
1322 __all__.append('get_override_type')
1323
1324 ################################################################################
1325
1326 class PendingContentAssociation(object):
1327     def __init__(self, *args, **kwargs):
1328         pass
1329
1330     def __repr__(self):
1331         return '<PendingContentAssociation %s>' % self.pca_id
1332
1333 __all__.append('PendingContentAssociation')
1334
1335 def insert_pending_content_paths(package, fullpaths, session=None):
1336     """
1337     Make sure given paths are temporarily associated with given
1338     package
1339
1340     @type package: dict
1341     @param package: the package to associate with should have been read in from the binary control file
1342     @type fullpaths: list
1343     @param fullpaths: the list of paths of the file being associated with the binary
1344     @type session: SQLAlchemy session
1345     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1346     is responsible for ensuring a transaction has begun and committing the
1347     results or rolling back based on the result code.  If not passed, a commit
1348     will be performed at the end of the function
1349
1350     @return: True upon success, False if there is a problem
1351     """
1352
1353     privatetrans = False
1354
1355     if session is None:
1356         session = DBConn().session()
1357         privatetrans = True
1358
1359     try:
1360         arch = get_architecture(package['Architecture'], session)
1361         arch_id = arch.arch_id
1362
1363         # Remove any already existing recorded files for this package
1364         q = session.query(PendingContentAssociation)
1365         q = q.filter_by(package=package['Package'])
1366         q = q.filter_by(version=package['Version'])
1367         q = q.filter_by(architecture=arch_id)
1368         q.delete()
1369
1370         # Insert paths
1371         pathcache = {}
1372         for fullpath in fullpaths:
1373             (path, file) = os.path.split(fullpath)
1374
1375             if path.startswith( "./" ):
1376                 path = path[2:]
1377
1378             filepath_id = get_or_set_contents_path_id(path, session)
1379             filename_id = get_or_set_contents_file_id(file, session)
1380
1381             pathcache[fullpath] = (filepath_id, filename_id)
1382
1383         for fullpath, dat in pathcache.items():
1384             pca = PendingContentAssociation()
1385             pca.package = package['Package']
1386             pca.version = package['Version']
1387             pca.filepath_id = dat[0]
1388             pca.filename_id = dat[1]
1389             pca.architecture = arch_id
1390             session.add(pca)
1391
1392         # Only commit if we set up the session ourself
1393         if privatetrans:
1394             session.commit()
1395             session.close()
1396         else:
1397             session.flush()
1398
1399         return True
1400     except Exception, e:
1401         traceback.print_exc()
1402
1403         # Only rollback if we set up the session ourself
1404         if privatetrans:
1405             session.rollback()
1406             session.close()
1407
1408         return False
1409
1410 __all__.append('insert_pending_content_paths')
1411
1412 ################################################################################
1413
1414 class Priority(object):
1415     def __init__(self, *args, **kwargs):
1416         pass
1417
1418     def __eq__(self, val):
1419         if isinstance(val, str):
1420             return (self.priority == val)
1421         # This signals to use the normal comparison operator
1422         return NotImplemented
1423
1424     def __ne__(self, val):
1425         if isinstance(val, str):
1426             return (self.priority != val)
1427         # This signals to use the normal comparison operator
1428         return NotImplemented
1429
1430     def __repr__(self):
1431         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1432
1433 __all__.append('Priority')
1434
1435 def get_priority(priority, session=None):
1436     """
1437     Returns Priority object for given C{priority name}.
1438
1439     @type priority: string
1440     @param priority: The name of the priority
1441
1442     @type session: Session
1443     @param session: Optional SQLA session object (a temporary one will be
1444     generated if not supplied)
1445
1446     @rtype: Priority
1447     @return: Priority object for the given priority
1448
1449     """
1450     privatetrans = False
1451     if session is None:
1452         session = DBConn().session()
1453         privatetrans = True
1454
1455     q = session.query(Priority).filter_by(priority=priority)
1456
1457     if q.count() == 0:
1458         ret = None
1459     else:
1460         ret = q.one()
1461
1462     if privatetrans:
1463         session.close()
1464
1465     return ret
1466
1467 __all__.append('get_priority')
1468
1469 def get_priorities(session=None):
1470     """
1471     Returns dictionary of priority names -> id mappings
1472
1473     @type session: Session
1474     @param session: Optional SQL session object (a temporary one will be
1475     generated if not supplied)
1476
1477     @rtype: dictionary
1478     @return: dictionary of priority names -> id mappings
1479     """
1480     privatetrans = False
1481     if session is None:
1482         session = DBConn().session()
1483         privatetrans = True
1484
1485     ret = {}
1486     q = session.query(Priority)
1487     for x in q.all():
1488         ret[x.priority] = x.priority_id
1489
1490     if privatetrans:
1491         session.close()
1492
1493     return ret
1494
1495 __all__.append('get_priorities')
1496
1497 ################################################################################
1498
1499 class Queue(object):
1500     def __init__(self, *args, **kwargs):
1501         pass
1502
1503     def __repr__(self):
1504         return '<Queue %s>' % self.queue_name
1505
1506     def autobuild_upload(self, changes, srcpath, session=None):
1507         """
1508         Update queue_build database table used for incoming autobuild support.
1509
1510         @type changes: Changes
1511         @param changes: changes object for the upload to process
1512
1513         @type srcpath: string
1514         @param srcpath: path for the queue file entries/link destinations
1515
1516         @type session: SQLAlchemy session
1517         @param session: Optional SQLAlchemy session.  If this is passed, the
1518         caller is responsible for ensuring a transaction has begun and
1519         committing the results or rolling back based on the result code.  If
1520         not passed, a commit will be performed at the end of the function,
1521         otherwise the caller is responsible for commiting.
1522
1523         @rtype: NoneType or string
1524         @return: None if the operation failed, a string describing the error if not
1525         """
1526
1527         privatetrans = False
1528         if session is None:
1529             session = DBConn().session()
1530             privatetrans = True
1531
1532         # TODO: Remove by moving queue config into the database
1533         conf = Config()
1534
1535         for suitename in changes.changes["distribution"].keys():
1536             # TODO: Move into database as:
1537             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1538             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1539             #       This also gets rid of the SecurityQueueBuild hack below
1540             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1541                 continue
1542
1543             # Find suite object
1544             s = get_suite(suitename, session)
1545             if s is None:
1546                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1547
1548             # TODO: Get from database as above
1549             dest_dir = conf["Dir::QueueBuild"]
1550
1551             # TODO: Move into database as above
1552             if conf.FindB("Dinstall::SecurityQueueBuild"):
1553                 dest_dir = os.path.join(dest_dir, suitename)
1554
1555             for file_entry in changes.files.keys():
1556                 src = os.path.join(srcpath, file_entry)
1557                 dest = os.path.join(dest_dir, file_entry)
1558
1559                 # TODO: Move into database as above
1560                 if conf.FindB("Dinstall::SecurityQueueBuild"):
1561                     # Copy it since the original won't be readable by www-data
1562                     utils.copy(src, dest)
1563                 else:
1564                     # Create a symlink to it
1565                     os.symlink(src, dest)
1566
1567                 qb = QueueBuild()
1568                 qb.suite_id = s.suite_id
1569                 qb.queue_id = self.queue_id
1570                 qb.filename = dest
1571                 qb.in_queue = True
1572
1573                 session.add(qb)
1574
1575             # If the .orig.tar.gz is in the pool, create a symlink to
1576             # it (if one doesn't already exist)
1577             if changes.orig_tar_id:
1578                 # Determine the .orig.tar.gz file name
1579                 for dsc_file in changes.dsc_files.keys():
1580                     if dsc_file.endswith(".orig.tar.gz"):
1581                         filename = dsc_file
1582
1583                 dest = os.path.join(dest_dir, filename)
1584
1585                 # If it doesn't exist, create a symlink
1586                 if not os.path.exists(dest):
1587                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1588                                         {'id': changes.orig_tar_id})
1589                     res = q.fetchone()
1590                     if not res:
1591                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1592
1593                     src = os.path.join(res[0], res[1])
1594                     os.symlink(src, dest)
1595
1596                     # Add it to the list of packages for later processing by apt-ftparchive
1597                     qb = QueueBuild()
1598                     qb.suite_id = s.suite_id
1599                     qb.queue_id = self.queue_id
1600                     qb.filename = dest
1601                     qb.in_queue = True
1602                     session.add(qb)
1603
1604                 # If it does, update things to ensure it's not removed prematurely
1605                 else:
1606                     qb = get_queue_build(dest, s.suite_id, session)
1607                     if qb is None:
1608                         qb.in_queue = True
1609                         qb.last_used = None
1610                         session.add(qb)
1611
1612         if privatetrans:
1613             session.commit()
1614             session.close()
1615
1616         return None
1617
1618 __all__.append('Queue')
1619
1620 def get_queue(queuename, session=None):
1621     """
1622     Returns Queue object for given C{queue name}.
1623
1624     @type queuename: string
1625     @param queuename: The name of the queue
1626
1627     @type session: Session
1628     @param session: Optional SQLA session object (a temporary one will be
1629     generated if not supplied)
1630
1631     @rtype: Queue
1632     @return: Queue object for the given queue
1633
1634     """
1635     privatetrans = False
1636     if session is None:
1637         session = DBConn().session()
1638         privatetrans = True
1639
1640     q = session.query(Queue).filter_by(queue_name=queuename)
1641     if q.count() == 0:
1642         ret = None
1643     else:
1644         ret = q.one()
1645
1646     if privatetrans:
1647         session.close()
1648
1649     return ret
1650
1651 __all__.append('get_queue')
1652
1653 ################################################################################
1654
1655 class QueueBuild(object):
1656     def __init__(self, *args, **kwargs):
1657         pass
1658
1659     def __repr__(self):
1660         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1661
1662 __all__.append('QueueBuild')
1663
1664 def get_queue_build(filename, suite, session=None):
1665     """
1666     Returns QueueBuild object for given C{filename} and C{suite}.
1667
1668     @type filename: string
1669     @param filename: The name of the file
1670
1671     @type suiteid: int or str
1672     @param suiteid: Suite name or ID
1673
1674     @type session: Session
1675     @param session: Optional SQLA session object (a temporary one will be
1676     generated if not supplied)
1677
1678     @rtype: Queue
1679     @return: Queue object for the given queue
1680
1681     """
1682     privatetrans = False
1683     if session is None:
1684         session = DBConn().session()
1685         privatetrans = True
1686
1687     if isinstance(suite, int):
1688         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1689     else:
1690         q = session.query(QueueBuild).filter_by(filename=filename)
1691         q = q.join(Suite).filter_by(suite_name=suite)
1692
1693     if q.count() == 0:
1694         ret = None
1695     else:
1696         ret = q.one()
1697
1698     if privatetrans:
1699         session.close()
1700
1701     return ret
1702
1703 __all__.append('get_queue_build')
1704
1705 ################################################################################
1706
1707 class Section(object):
1708     def __init__(self, *args, **kwargs):
1709         pass
1710
1711     def __eq__(self, val):
1712         if isinstance(val, str):
1713             return (self.section == val)
1714         # This signals to use the normal comparison operator
1715         return NotImplemented
1716
1717     def __ne__(self, val):
1718         if isinstance(val, str):
1719             return (self.section != val)
1720         # This signals to use the normal comparison operator
1721         return NotImplemented
1722
1723     def __repr__(self):
1724         return '<Section %s>' % self.section
1725
1726 __all__.append('Section')
1727
1728 def get_section(section, session=None):
1729     """
1730     Returns Section object for given C{section name}.
1731
1732     @type section: string
1733     @param section: The name of the section
1734
1735     @type session: Session
1736     @param session: Optional SQLA session object (a temporary one will be
1737     generated if not supplied)
1738
1739     @rtype: Section
1740     @return: Section object for the given section name
1741
1742     """
1743     privatetrans = False
1744     if session is None:
1745         session = DBConn().session()
1746         privatetrans = True
1747
1748     q = session.query(Section).filter_by(section=section)
1749     if q.count() == 0:
1750         ret = None
1751     else:
1752         ret = q.one()
1753
1754     if privatetrans:
1755         session.close()
1756
1757     return ret
1758
1759 __all__.append('get_section')
1760
1761 def get_sections(session=None):
1762     """
1763     Returns dictionary of section names -> id mappings
1764
1765     @type session: Session
1766     @param session: Optional SQL session object (a temporary one will be
1767     generated if not supplied)
1768
1769     @rtype: dictionary
1770     @return: dictionary of section names -> id mappings
1771     """
1772     privatetrans = False
1773     if session is None:
1774         session = DBConn().session()
1775         privatetrans = True
1776
1777     ret = {}
1778     q = session.query(Section)
1779     for x in q.all():
1780         ret[x.section] = x.section_id
1781
1782     if privatetrans:
1783         session.close()
1784
1785     return ret
1786
1787 __all__.append('get_sections')
1788
1789 ################################################################################
1790
1791 class DBSource(object):
1792     def __init__(self, *args, **kwargs):
1793         pass
1794
1795     def __repr__(self):
1796         return '<DBSource %s (%s)>' % (self.source, self.version)
1797
1798 __all__.append('DBSource')
1799
1800 def source_exists(source, source_version, suites = ["any"], session=None):
1801     """
1802     Ensure that source exists somewhere in the archive for the binary
1803     upload being processed.
1804       1. exact match     => 1.0-3
1805       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1806
1807     @type package: string
1808     @param package: package source name
1809
1810     @type source_version: string
1811     @param source_version: expected source version
1812
1813     @type suites: list
1814     @param suites: list of suites to check in, default I{any}
1815
1816     @type session: Session
1817     @param session: Optional SQLA session object (a temporary one will be
1818     generated if not supplied)
1819
1820     @rtype: int
1821     @return: returns 1 if a source with expected version is found, otherwise 0
1822
1823     """
1824
1825     privatetrans = False
1826     if session is None:
1827         session = DBConn().session()
1828         privatetrans = True
1829
1830     cnf = Config()
1831     ret = 1
1832
1833     for suite in suites:
1834         q = session.query(DBSource).filter_by(source=source)
1835         if suite != "any":
1836             # source must exist in suite X, or in some other suite that's
1837             # mapped to X, recursively... silent-maps are counted too,
1838             # unreleased-maps aren't.
1839             maps = cnf.ValueList("SuiteMappings")[:]
1840             maps.reverse()
1841             maps = [ m.split() for m in maps ]
1842             maps = [ (x[1], x[2]) for x in maps
1843                             if x[0] == "map" or x[0] == "silent-map" ]
1844             s = [suite]
1845             for x in maps:
1846                 if x[1] in s and x[0] not in s:
1847                     s.append(x[0])
1848
1849             q = q.join(SrcAssociation).join(Suite)
1850             q = q.filter(Suite.suite_name.in_(s))
1851
1852         # Reduce the query results to a list of version numbers
1853         ql = [ j.version for j in q.all() ]
1854
1855         # Try (1)
1856         if source_version in ql:
1857             continue
1858
1859         # Try (2)
1860         from daklib.regexes import re_bin_only_nmu
1861         orig_source_version = re_bin_only_nmu.sub('', source_version)
1862         if orig_source_version in ql:
1863             continue
1864
1865         # No source found so return not ok
1866         ret = 0
1867
1868     if privatetrans:
1869         session.close()
1870
1871     return ret
1872
1873 __all__.append('source_exists')
1874
1875 def get_suites_source_in(source, session=None):
1876     """
1877     Returns list of Suite objects which given C{source} name is in
1878
1879     @type source: str
1880     @param source: DBSource package name to search for
1881
1882     @rtype: list
1883     @return: list of Suite objects for the given source
1884     """
1885
1886     privatetrans = False
1887     if session is None:
1888         session = DBConn().session()
1889         privatetrans = True
1890
1891     ret = session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1892
1893     if privatetrans:
1894         session.close()
1895
1896     return ret
1897
1898 __all__.append('get_suites_source_in')
1899
1900 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1901     """
1902     Returns list of DBSource objects for given C{source} name and other parameters
1903
1904     @type source: str
1905     @param source: DBSource package name to search for
1906
1907     @type source: str or None
1908     @param source: DBSource version name to search for or None if not applicable
1909
1910     @type dm_upload_allowed: bool
1911     @param dm_upload_allowed: If None, no effect.  If True or False, only
1912     return packages with that dm_upload_allowed setting
1913
1914     @type session: Session
1915     @param session: Optional SQL session object (a temporary one will be
1916     generated if not supplied)
1917
1918     @rtype: list
1919     @return: list of DBSource objects for the given name (may be empty)
1920     """
1921     privatetrans = False
1922     if session is None:
1923         session = DBConn().session()
1924         privatetrans = True
1925
1926     q = session.query(DBSource).filter_by(source=source)
1927
1928     if version is not None:
1929         q = q.filter_by(version=version)
1930
1931     if dm_upload_allowed is not None:
1932         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1933
1934     ret = q.all()
1935
1936     if privatetrans:
1937         session.close()
1938
1939     return ret
1940
1941 __all__.append('get_sources_from_name')
1942
1943 def get_source_in_suite(source, suite, session=None):
1944     """
1945     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1946
1947       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1948       - B{suite} - a suite name, eg. I{unstable}
1949
1950     @type source: string
1951     @param source: source package name
1952
1953     @type suite: string
1954     @param suite: the suite name
1955
1956     @rtype: string
1957     @return: the version for I{source} in I{suite}
1958
1959     """
1960     privatetrans = False
1961     if session is None:
1962         session = DBConn().session()
1963         privatetrans = True
1964
1965     q = session.query(SrcAssociation)
1966     q = q.join('source').filter_by(source=source)
1967     q = q.join('suite').filter_by(suite_name=suite)
1968
1969     if q.count() == 0:
1970         ret =  None
1971     else:
1972         # ???: Maybe we should just return the SrcAssociation object instead
1973         ret = q.one().source
1974
1975     if privatetrans:
1976         session.close()
1977
1978     return ret
1979
1980 __all__.append('get_source_in_suite')
1981
1982 ################################################################################
1983
1984 class SrcAssociation(object):
1985     def __init__(self, *args, **kwargs):
1986         pass
1987
1988     def __repr__(self):
1989         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1990
1991 __all__.append('SrcAssociation')
1992
1993 ################################################################################
1994
1995 class SrcUploader(object):
1996     def __init__(self, *args, **kwargs):
1997         pass
1998
1999     def __repr__(self):
2000         return '<SrcUploader %s>' % self.uploader_id
2001
2002 __all__.append('SrcUploader')
2003
2004 ################################################################################
2005
2006 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2007                  ('SuiteID', 'suite_id'),
2008                  ('Version', 'version'),
2009                  ('Origin', 'origin'),
2010                  ('Label', 'label'),
2011                  ('Description', 'description'),
2012                  ('Untouchable', 'untouchable'),
2013                  ('Announce', 'announce'),
2014                  ('Codename', 'codename'),
2015                  ('OverrideCodename', 'overridecodename'),
2016                  ('ValidTime', 'validtime'),
2017                  ('Priority', 'priority'),
2018                  ('NotAutomatic', 'notautomatic'),
2019                  ('CopyChanges', 'copychanges'),
2020                  ('CopyDotDak', 'copydotdak'),
2021                  ('CommentsDir', 'commentsdir'),
2022                  ('OverrideSuite', 'overridesuite'),
2023                  ('ChangelogBase', 'changelogbase')]
2024
2025
2026 class Suite(object):
2027     def __init__(self, *args, **kwargs):
2028         pass
2029
2030     def __repr__(self):
2031         return '<Suite %s>' % self.suite_name
2032
2033     def __eq__(self, val):
2034         if isinstance(val, str):
2035             return (self.suite_name == val)
2036         # This signals to use the normal comparison operator
2037         return NotImplemented
2038
2039     def __ne__(self, val):
2040         if isinstance(val, str):
2041             return (self.suite_name != val)
2042         # This signals to use the normal comparison operator
2043         return NotImplemented
2044
2045     def details(self):
2046         ret = []
2047         for disp, field in SUITE_FIELDS:
2048             val = getattr(self, field, None)
2049             if val is not None:
2050                 ret.append("%s: %s" % (disp, val))
2051
2052         return "\n".join(ret)
2053
2054 __all__.append('Suite')
2055
2056 def get_suite_architecture(suite, architecture, session=None):
2057     """
2058     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2059     doesn't exist
2060
2061     @type suite: str
2062     @param suite: Suite name to search for
2063
2064     @type architecture: str
2065     @param architecture: Architecture name to search for
2066
2067     @type session: Session
2068     @param session: Optional SQL session object (a temporary one will be
2069     generated if not supplied)
2070
2071     @rtype: SuiteArchitecture
2072     @return: the SuiteArchitecture object or None
2073     """
2074
2075     privatetrans = False
2076     if session is None:
2077         session = DBConn().session()
2078         privatetrans = True
2079
2080     q = session.query(SuiteArchitecture)
2081     q = q.join(Architecture).filter_by(arch_string=architecture)
2082     q = q.join(Suite).filter_by(suite_name=suite)
2083
2084     if q.count() == 0:
2085         ret = None
2086     else:
2087         ret = q.one()
2088
2089     if privatetrans:
2090         session.close()
2091
2092     return ret
2093
2094 __all__.append('get_suite_architecture')
2095
2096 def get_suite(suite, session=None):
2097     """
2098     Returns Suite object for given C{suite name}.
2099
2100     @type suite: string
2101     @param suite: The name of the suite
2102
2103     @type session: Session
2104     @param session: Optional SQLA session object (a temporary one will be
2105     generated if not supplied)
2106
2107     @rtype: Suite
2108     @return: Suite object for the requested suite name (None if not presenT)
2109
2110     """
2111     privatetrans = False
2112     if session is None:
2113         session = DBConn().session()
2114         privatetrans = True
2115
2116     q = session.query(Suite).filter_by(suite_name=suite)
2117
2118     if q.count() == 0:
2119         ret = None
2120     else:
2121         ret = q.one()
2122
2123     if privatetrans:
2124         session.close()
2125
2126     return ret
2127
2128 __all__.append('get_suite')
2129
2130 ################################################################################
2131
2132 class SuiteArchitecture(object):
2133     def __init__(self, *args, **kwargs):
2134         pass
2135
2136     def __repr__(self):
2137         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2138
2139 __all__.append('SuiteArchitecture')
2140
2141 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2142     """
2143     Returns list of Architecture objects for given C{suite} name
2144
2145     @type source: str
2146     @param source: Suite name to search for
2147
2148     @type skipsrc: boolean
2149     @param skipsrc: Whether to skip returning the 'source' architecture entry
2150     (Default False)
2151
2152     @type skipall: boolean
2153     @param skipall: Whether to skip returning the 'all' architecture entry
2154     (Default False)
2155
2156     @type session: Session
2157     @param session: Optional SQL session object (a temporary one will be
2158     generated if not supplied)
2159
2160     @rtype: list
2161     @return: list of Architecture objects for the given name (may be empty)
2162     """
2163
2164     privatetrans = False
2165     if session is None:
2166         session = DBConn().session()
2167         privatetrans = True
2168
2169     q = session.query(Architecture)
2170     q = q.join(SuiteArchitecture)
2171     q = q.join(Suite).filter_by(suite_name=suite)
2172
2173     if skipsrc:
2174         q = q.filter(Architecture.arch_string != 'source')
2175
2176     if skipall:
2177         q = q.filter(Architecture.arch_string != 'all')
2178
2179     q = q.order_by('arch_string')
2180
2181     ret = q.all()
2182
2183     if privatetrans:
2184         session.close()
2185
2186     return ret
2187
2188 __all__.append('get_suite_architectures')
2189
2190 ################################################################################
2191
2192 class Uid(object):
2193     def __init__(self, *args, **kwargs):
2194         pass
2195
2196     def __eq__(self, val):
2197         if isinstance(val, str):
2198             return (self.uid == val)
2199         # This signals to use the normal comparison operator
2200         return NotImplemented
2201
2202     def __ne__(self, val):
2203         if isinstance(val, str):
2204             return (self.uid != val)
2205         # This signals to use the normal comparison operator
2206         return NotImplemented
2207
2208     def __repr__(self):
2209         return '<Uid %s (%s)>' % (self.uid, self.name)
2210
2211 __all__.append('Uid')
2212
2213 def add_database_user(uidname, session=None):
2214     """
2215     Adds a database user
2216
2217     @type uidname: string
2218     @param uidname: The uid of the user to add
2219
2220     @type session: SQLAlchemy
2221     @param session: Optional SQL session object (a temporary one will be
2222     generated if not supplied).  If not passed, a commit will be performed at
2223     the end of the function, otherwise the caller is responsible for commiting.
2224
2225     @rtype: Uid
2226     @return: the uid object for the given uidname
2227     """
2228
2229     privatetrans = False
2230     if session is None:
2231         session = DBConn().session()
2232         privatetrans = True
2233
2234     session.execute("CREATE USER :uid", {'uid': uidname})
2235
2236     if privatetrans:
2237         session.commit()
2238         session.close()
2239
2240 __all__.append('add_database_user')
2241
2242 def get_or_set_uid(uidname, session=None):
2243     """
2244     Returns uid object for given uidname.
2245
2246     If no matching uidname is found, a row is inserted.
2247
2248     @type uidname: string
2249     @param uidname: The uid to add
2250
2251     @type session: SQLAlchemy
2252     @param session: Optional SQL session object (a temporary one will be
2253     generated if not supplied).  If not passed, a commit will be performed at
2254     the end of the function, otherwise the caller is responsible for commiting.
2255
2256     @rtype: Uid
2257     @return: the uid object for the given uidname
2258     """
2259
2260     privatetrans = False
2261     if session is None:
2262         session = DBConn().session()
2263         privatetrans = True
2264
2265     q = session.query(Uid).filter_by(uid=uidname)
2266
2267     if q.count() < 1:
2268         uid = Uid()
2269         uid.uid = uidname
2270         session.add(uid)
2271         if privatetrans:
2272             session.commit()
2273         else:
2274             session.flush()
2275         ret = uid
2276     else:
2277         ret = q.one()
2278
2279     if privatetrans:
2280         session.close()
2281
2282     return ret
2283
2284 __all__.append('get_or_set_uid')
2285
2286
2287 def get_uid_from_fingerprint(fpr, session=None):
2288     privatetrans = False
2289     if session is None:
2290         session = DBConn().session()
2291         privatetrans = True
2292
2293     q = session.query(Uid)
2294     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2295
2296     if q.count() != 1:
2297         ret = None
2298     else:
2299         ret = q.one()
2300
2301     if privatetrans:
2302         session.close()
2303
2304     return ret
2305
2306 __all__.append('get_uid_from_fingerprint')
2307
2308 ################################################################################
2309
2310 class DBConn(Singleton):
2311     """
2312     database module init.
2313     """
2314     def __init__(self, *args, **kwargs):
2315         super(DBConn, self).__init__(*args, **kwargs)
2316
2317     def _startup(self, *args, **kwargs):
2318         self.debug = False
2319         if kwargs.has_key('debug'):
2320             self.debug = True
2321         self.__createconn()
2322
2323     def __setuptables(self):
2324         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2325         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2326         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2327         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2328         self.tbl_component = Table('component', self.db_meta, autoload=True)
2329         self.tbl_config = Table('config', self.db_meta, autoload=True)
2330         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2331         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2332         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2333         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2334         self.tbl_files = Table('files', self.db_meta, autoload=True)
2335         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2336         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2337         self.tbl_location = Table('location', self.db_meta, autoload=True)
2338         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2339         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2340         self.tbl_override = Table('override', self.db_meta, autoload=True)
2341         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2342         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2343         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2344         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2345         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2346         self.tbl_section = Table('section', self.db_meta, autoload=True)
2347         self.tbl_source = Table('source', self.db_meta, autoload=True)
2348         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2349         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2350         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2351         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2352         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2353
2354     def __setupmappers(self):
2355         mapper(Architecture, self.tbl_architecture,
2356                properties = dict(arch_id = self.tbl_architecture.c.id))
2357
2358         mapper(Archive, self.tbl_archive,
2359                properties = dict(archive_id = self.tbl_archive.c.id,
2360                                  archive_name = self.tbl_archive.c.name))
2361
2362         mapper(BinAssociation, self.tbl_bin_associations,
2363                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2364                                  suite_id = self.tbl_bin_associations.c.suite,
2365                                  suite = relation(Suite),
2366                                  binary_id = self.tbl_bin_associations.c.bin,
2367                                  binary = relation(DBBinary)))
2368
2369         mapper(DBBinary, self.tbl_binaries,
2370                properties = dict(binary_id = self.tbl_binaries.c.id,
2371                                  package = self.tbl_binaries.c.package,
2372                                  version = self.tbl_binaries.c.version,
2373                                  maintainer_id = self.tbl_binaries.c.maintainer,
2374                                  maintainer = relation(Maintainer),
2375                                  source_id = self.tbl_binaries.c.source,
2376                                  source = relation(DBSource),
2377                                  arch_id = self.tbl_binaries.c.architecture,
2378                                  architecture = relation(Architecture),
2379                                  poolfile_id = self.tbl_binaries.c.file,
2380                                  poolfile = relation(PoolFile),
2381                                  binarytype = self.tbl_binaries.c.type,
2382                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2383                                  fingerprint = relation(Fingerprint),
2384                                  install_date = self.tbl_binaries.c.install_date,
2385                                  binassociations = relation(BinAssociation,
2386                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2387
2388         mapper(Component, self.tbl_component,
2389                properties = dict(component_id = self.tbl_component.c.id,
2390                                  component_name = self.tbl_component.c.name))
2391
2392         mapper(DBConfig, self.tbl_config,
2393                properties = dict(config_id = self.tbl_config.c.id))
2394
2395         mapper(ContentAssociation, self.tbl_content_associations,
2396                properties = dict(ca_id = self.tbl_content_associations.c.id,
2397                                  filename_id = self.tbl_content_associations.c.filename,
2398                                  filename    = relation(ContentFilename),
2399                                  filepath_id = self.tbl_content_associations.c.filepath,
2400                                  filepath    = relation(ContentFilepath),
2401                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
2402                                  binary      = relation(DBBinary)))
2403
2404
2405         mapper(ContentFilename, self.tbl_content_file_names,
2406                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2407                                  filename = self.tbl_content_file_names.c.file))
2408
2409         mapper(ContentFilepath, self.tbl_content_file_paths,
2410                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2411                                  filepath = self.tbl_content_file_paths.c.path))
2412
2413         mapper(DSCFile, self.tbl_dsc_files,
2414                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2415                                  source_id = self.tbl_dsc_files.c.source,
2416                                  source = relation(DBSource),
2417                                  poolfile_id = self.tbl_dsc_files.c.file,
2418                                  poolfile = relation(PoolFile)))
2419
2420         mapper(PoolFile, self.tbl_files,
2421                properties = dict(file_id = self.tbl_files.c.id,
2422                                  filesize = self.tbl_files.c.size,
2423                                  location_id = self.tbl_files.c.location,
2424                                  location = relation(Location)))
2425
2426         mapper(Fingerprint, self.tbl_fingerprint,
2427                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2428                                  uid_id = self.tbl_fingerprint.c.uid,
2429                                  uid = relation(Uid),
2430                                  keyring_id = self.tbl_fingerprint.c.keyring,
2431                                  keyring = relation(Keyring)))
2432
2433         mapper(Keyring, self.tbl_keyrings,
2434                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2435                                  keyring_id = self.tbl_keyrings.c.id))
2436
2437         mapper(Location, self.tbl_location,
2438                properties = dict(location_id = self.tbl_location.c.id,
2439                                  component_id = self.tbl_location.c.component,
2440                                  component = relation(Component),
2441                                  archive_id = self.tbl_location.c.archive,
2442                                  archive = relation(Archive),
2443                                  archive_type = self.tbl_location.c.type))
2444
2445         mapper(Maintainer, self.tbl_maintainer,
2446                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2447
2448         mapper(NewComment, self.tbl_new_comments,
2449                properties = dict(comment_id = self.tbl_new_comments.c.id))
2450
2451         mapper(Override, self.tbl_override,
2452                properties = dict(suite_id = self.tbl_override.c.suite,
2453                                  suite = relation(Suite),
2454                                  component_id = self.tbl_override.c.component,
2455                                  component = relation(Component),
2456                                  priority_id = self.tbl_override.c.priority,
2457                                  priority = relation(Priority),
2458                                  section_id = self.tbl_override.c.section,
2459                                  section = relation(Section),
2460                                  overridetype_id = self.tbl_override.c.type,
2461                                  overridetype = relation(OverrideType)))
2462
2463         mapper(OverrideType, self.tbl_override_type,
2464                properties = dict(overridetype = self.tbl_override_type.c.type,
2465                                  overridetype_id = self.tbl_override_type.c.id))
2466
2467         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2468                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2469                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
2470                                  filepath = relation(ContentFilepath),
2471                                  filename_id = self.tbl_pending_content_associations.c.filename,
2472                                  filename = relation(ContentFilename)))
2473
2474         mapper(Priority, self.tbl_priority,
2475                properties = dict(priority_id = self.tbl_priority.c.id))
2476
2477         mapper(Queue, self.tbl_queue,
2478                properties = dict(queue_id = self.tbl_queue.c.id))
2479
2480         mapper(QueueBuild, self.tbl_queue_build,
2481                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2482                                  queue_id = self.tbl_queue_build.c.queue,
2483                                  queue = relation(Queue, backref='queuebuild')))
2484
2485         mapper(Section, self.tbl_section,
2486                properties = dict(section_id = self.tbl_section.c.id))
2487
2488         mapper(DBSource, self.tbl_source,
2489                properties = dict(source_id = self.tbl_source.c.id,
2490                                  version = self.tbl_source.c.version,
2491                                  maintainer_id = self.tbl_source.c.maintainer,
2492                                  maintainer = relation(Maintainer,
2493                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2494                                  poolfile_id = self.tbl_source.c.file,
2495                                  poolfile = relation(PoolFile),
2496                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2497                                  fingerprint = relation(Fingerprint),
2498                                  changedby_id = self.tbl_source.c.changedby,
2499                                  changedby = relation(Maintainer,
2500                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2501                                  srcfiles = relation(DSCFile,
2502                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2503                                  srcassociations = relation(SrcAssociation,
2504                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2505
2506         mapper(SrcAssociation, self.tbl_src_associations,
2507                properties = dict(sa_id = self.tbl_src_associations.c.id,
2508                                  suite_id = self.tbl_src_associations.c.suite,
2509                                  suite = relation(Suite),
2510                                  source_id = self.tbl_src_associations.c.source,
2511                                  source = relation(DBSource)))
2512
2513         mapper(SrcUploader, self.tbl_src_uploaders,
2514                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2515                                  source_id = self.tbl_src_uploaders.c.source,
2516                                  source = relation(DBSource,
2517                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2518                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2519                                  maintainer = relation(Maintainer,
2520                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2521
2522         mapper(Suite, self.tbl_suite,
2523                properties = dict(suite_id = self.tbl_suite.c.id))
2524
2525         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2526                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2527                                  suite = relation(Suite, backref='suitearchitectures'),
2528                                  arch_id = self.tbl_suite_architectures.c.architecture,
2529                                  architecture = relation(Architecture)))
2530
2531         mapper(Uid, self.tbl_uid,
2532                properties = dict(uid_id = self.tbl_uid.c.id,
2533                                  fingerprint = relation(Fingerprint)))
2534
2535     ## Connection functions
2536     def __createconn(self):
2537         from config import Config
2538         cnf = Config()
2539         if cnf["DB::Host"]:
2540             # TCP/IP
2541             connstr = "postgres://%s" % cnf["DB::Host"]
2542             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2543                 connstr += ":%s" % cnf["DB::Port"]
2544             connstr += "/%s" % cnf["DB::Name"]
2545         else:
2546             # Unix Socket
2547             connstr = "postgres:///%s" % cnf["DB::Name"]
2548             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2549                 connstr += "?port=%s" % cnf["DB::Port"]
2550
2551         self.db_pg   = create_engine(connstr, echo=self.debug)
2552         self.db_meta = MetaData()
2553         self.db_meta.bind = self.db_pg
2554         self.db_smaker = sessionmaker(bind=self.db_pg,
2555                                       autoflush=True,
2556                                       autocommit=False)
2557
2558         self.__setuptables()
2559         self.__setupmappers()
2560
2561     def session(self):
2562         return self.db_smaker()
2563
2564 __all__.append('DBConn')
2565
2566