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