]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
ff00135f0998f339919feefb778a6e05030acf47
[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.tar.gz is in the pool, create a symlink to
1613             # it (if one doesn't already exist)
1614             if changes.orig_tar_id:
1615                 # Determine the .orig.tar.gz file name
1616                 for dsc_file in changes.dsc_files.keys():
1617                     if dsc_file.endswith(".orig.tar.gz"):
1618                         filename = dsc_file
1619
1620                 dest = os.path.join(dest_dir, filename)
1621
1622                 # If it doesn't exist, create a symlink
1623                 if not os.path.exists(dest):
1624                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1625                                         {'id': changes.orig_tar_id})
1626                     res = q.fetchone()
1627                     if not res:
1628                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1629
1630                     src = os.path.join(res[0], res[1])
1631                     os.symlink(src, dest)
1632
1633                     # Add it to the list of packages for later processing by apt-ftparchive
1634                     qb = QueueBuild()
1635                     qb.suite_id = s.suite_id
1636                     qb.queue_id = self.queue_id
1637                     qb.filename = dest
1638                     qb.in_queue = True
1639                     session.add(qb)
1640
1641                 # If it does, update things to ensure it's not removed prematurely
1642                 else:
1643                     qb = get_queue_build(dest, s.suite_id, session)
1644                     if qb is None:
1645                         qb.in_queue = True
1646                         qb.last_used = None
1647                         session.add(qb)
1648
1649         if privatetrans:
1650             session.commit()
1651             session.close()
1652
1653         return None
1654
1655 __all__.append('Queue')
1656
1657 def get_queue(queuename, session=None):
1658     """
1659     Returns Queue object for given C{queue name}.
1660
1661     @type queuename: string
1662     @param queuename: The name of the queue
1663
1664     @type session: Session
1665     @param session: Optional SQLA session object (a temporary one will be
1666     generated if not supplied)
1667
1668     @rtype: Queue
1669     @return: Queue object for the given queue
1670
1671     """
1672     privatetrans = False
1673     if session is None:
1674         session = DBConn().session()
1675         privatetrans = True
1676
1677     q = session.query(Queue).filter_by(queue_name=queuename)
1678     if q.count() == 0:
1679         ret = None
1680     else:
1681         ret = q.one()
1682
1683     if privatetrans:
1684         session.close()
1685
1686     return ret
1687
1688 __all__.append('get_queue')
1689
1690 ################################################################################
1691
1692 class QueueBuild(object):
1693     def __init__(self, *args, **kwargs):
1694         pass
1695
1696     def __repr__(self):
1697         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1698
1699 __all__.append('QueueBuild')
1700
1701 def get_queue_build(filename, suite, session=None):
1702     """
1703     Returns QueueBuild object for given C{filename} and C{suite}.
1704
1705     @type filename: string
1706     @param filename: The name of the file
1707
1708     @type suiteid: int or str
1709     @param suiteid: Suite name or ID
1710
1711     @type session: Session
1712     @param session: Optional SQLA session object (a temporary one will be
1713     generated if not supplied)
1714
1715     @rtype: Queue
1716     @return: Queue object for the given queue
1717
1718     """
1719     privatetrans = False
1720     if session is None:
1721         session = DBConn().session()
1722         privatetrans = True
1723
1724     if isinstance(suite, int):
1725         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1726     else:
1727         q = session.query(QueueBuild).filter_by(filename=filename)
1728         q = q.join(Suite).filter_by(suite_name=suite)
1729
1730     if q.count() == 0:
1731         ret = None
1732     else:
1733         ret = q.one()
1734
1735     if privatetrans:
1736         session.close()
1737
1738     return ret
1739
1740 __all__.append('get_queue_build')
1741
1742 ################################################################################
1743
1744 class Section(object):
1745     def __init__(self, *args, **kwargs):
1746         pass
1747
1748     def __eq__(self, val):
1749         if isinstance(val, str):
1750             return (self.section == val)
1751         # This signals to use the normal comparison operator
1752         return NotImplemented
1753
1754     def __ne__(self, val):
1755         if isinstance(val, str):
1756             return (self.section != val)
1757         # This signals to use the normal comparison operator
1758         return NotImplemented
1759
1760     def __repr__(self):
1761         return '<Section %s>' % self.section
1762
1763 __all__.append('Section')
1764
1765 def get_section(section, session=None):
1766     """
1767     Returns Section object for given C{section name}.
1768
1769     @type section: string
1770     @param section: The name of the section
1771
1772     @type session: Session
1773     @param session: Optional SQLA session object (a temporary one will be
1774     generated if not supplied)
1775
1776     @rtype: Section
1777     @return: Section object for the given section name
1778
1779     """
1780     privatetrans = False
1781     if session is None:
1782         session = DBConn().session()
1783         privatetrans = True
1784
1785     q = session.query(Section).filter_by(section=section)
1786     if q.count() == 0:
1787         ret = None
1788     else:
1789         ret = q.one()
1790
1791     if privatetrans:
1792         session.close()
1793
1794     return ret
1795
1796 __all__.append('get_section')
1797
1798 def get_sections(session=None):
1799     """
1800     Returns dictionary of section names -> id mappings
1801
1802     @type session: Session
1803     @param session: Optional SQL session object (a temporary one will be
1804     generated if not supplied)
1805
1806     @rtype: dictionary
1807     @return: dictionary of section names -> id mappings
1808     """
1809     privatetrans = False
1810     if session is None:
1811         session = DBConn().session()
1812         privatetrans = True
1813
1814     ret = {}
1815     q = session.query(Section)
1816     for x in q.all():
1817         ret[x.section] = x.section_id
1818
1819     if privatetrans:
1820         session.close()
1821
1822     return ret
1823
1824 __all__.append('get_sections')
1825
1826 ################################################################################
1827
1828 class DBSource(object):
1829     def __init__(self, *args, **kwargs):
1830         pass
1831
1832     def __repr__(self):
1833         return '<DBSource %s (%s)>' % (self.source, self.version)
1834
1835 __all__.append('DBSource')
1836
1837 def source_exists(source, source_version, suites = ["any"], session=None):
1838     """
1839     Ensure that source exists somewhere in the archive for the binary
1840     upload being processed.
1841       1. exact match     => 1.0-3
1842       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1843
1844     @type package: string
1845     @param package: package source name
1846
1847     @type source_version: string
1848     @param source_version: expected source version
1849
1850     @type suites: list
1851     @param suites: list of suites to check in, default I{any}
1852
1853     @type session: Session
1854     @param session: Optional SQLA session object (a temporary one will be
1855     generated if not supplied)
1856
1857     @rtype: int
1858     @return: returns 1 if a source with expected version is found, otherwise 0
1859
1860     """
1861
1862     privatetrans = False
1863     if session is None:
1864         session = DBConn().session()
1865         privatetrans = True
1866
1867     cnf = Config()
1868     ret = 1
1869
1870     for suite in suites:
1871         q = session.query(DBSource).filter_by(source=source)
1872         if suite != "any":
1873             # source must exist in suite X, or in some other suite that's
1874             # mapped to X, recursively... silent-maps are counted too,
1875             # unreleased-maps aren't.
1876             maps = cnf.ValueList("SuiteMappings")[:]
1877             maps.reverse()
1878             maps = [ m.split() for m in maps ]
1879             maps = [ (x[1], x[2]) for x in maps
1880                             if x[0] == "map" or x[0] == "silent-map" ]
1881             s = [suite]
1882             for x in maps:
1883                 if x[1] in s and x[0] not in s:
1884                     s.append(x[0])
1885
1886             q = q.join(SrcAssociation).join(Suite)
1887             q = q.filter(Suite.suite_name.in_(s))
1888
1889         # Reduce the query results to a list of version numbers
1890         ql = [ j.version for j in q.all() ]
1891
1892         # Try (1)
1893         if source_version in ql:
1894             continue
1895
1896         # Try (2)
1897         from daklib.regexes import re_bin_only_nmu
1898         orig_source_version = re_bin_only_nmu.sub('', source_version)
1899         if orig_source_version in ql:
1900             continue
1901
1902         # No source found so return not ok
1903         ret = 0
1904
1905     if privatetrans:
1906         session.close()
1907
1908     return ret
1909
1910 __all__.append('source_exists')
1911
1912 def get_suites_source_in(source, session=None):
1913     """
1914     Returns list of Suite objects which given C{source} name is in
1915
1916     @type source: str
1917     @param source: DBSource package name to search for
1918
1919     @rtype: list
1920     @return: list of Suite objects for the given source
1921     """
1922
1923     privatetrans = False
1924     if session is None:
1925         session = DBConn().session()
1926         privatetrans = True
1927
1928     ret = session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1929
1930     if privatetrans:
1931         session.close()
1932
1933     return ret
1934
1935 __all__.append('get_suites_source_in')
1936
1937 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1938     """
1939     Returns list of DBSource objects for given C{source} name and other parameters
1940
1941     @type source: str
1942     @param source: DBSource package name to search for
1943
1944     @type source: str or None
1945     @param source: DBSource version name to search for or None if not applicable
1946
1947     @type dm_upload_allowed: bool
1948     @param dm_upload_allowed: If None, no effect.  If True or False, only
1949     return packages with that dm_upload_allowed setting
1950
1951     @type session: Session
1952     @param session: Optional SQL session object (a temporary one will be
1953     generated if not supplied)
1954
1955     @rtype: list
1956     @return: list of DBSource objects for the given name (may be empty)
1957     """
1958     privatetrans = False
1959     if session is None:
1960         session = DBConn().session()
1961         privatetrans = True
1962
1963     q = session.query(DBSource).filter_by(source=source)
1964
1965     if version is not None:
1966         q = q.filter_by(version=version)
1967
1968     if dm_upload_allowed is not None:
1969         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1970
1971     ret = q.all()
1972
1973     if privatetrans:
1974         session.close()
1975
1976     return ret
1977
1978 __all__.append('get_sources_from_name')
1979
1980 def get_source_in_suite(source, suite, session=None):
1981     """
1982     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1983
1984       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1985       - B{suite} - a suite name, eg. I{unstable}
1986
1987     @type source: string
1988     @param source: source package name
1989
1990     @type suite: string
1991     @param suite: the suite name
1992
1993     @rtype: string
1994     @return: the version for I{source} in I{suite}
1995
1996     """
1997     privatetrans = False
1998     if session is None:
1999         session = DBConn().session()
2000         privatetrans = True
2001
2002     q = session.query(SrcAssociation)
2003     q = q.join('source').filter_by(source=source)
2004     q = q.join('suite').filter_by(suite_name=suite)
2005
2006     if q.count() == 0:
2007         ret =  None
2008     else:
2009         # ???: Maybe we should just return the SrcAssociation object instead
2010         ret = q.one().source
2011
2012     if privatetrans:
2013         session.close()
2014
2015     return ret
2016
2017 __all__.append('get_source_in_suite')
2018
2019 ################################################################################
2020
2021 class SrcAssociation(object):
2022     def __init__(self, *args, **kwargs):
2023         pass
2024
2025     def __repr__(self):
2026         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2027
2028 __all__.append('SrcAssociation')
2029
2030 ################################################################################
2031
2032 class SrcFormat(object):
2033     def __init__(self, *args, **kwargs):
2034         pass
2035
2036     def __repr__(self):
2037         return '<SrcFormat %s>' % (self.format_name)
2038
2039 __all__.append('SrcFormat')
2040
2041 ################################################################################
2042
2043 class SrcUploader(object):
2044     def __init__(self, *args, **kwargs):
2045         pass
2046
2047     def __repr__(self):
2048         return '<SrcUploader %s>' % self.uploader_id
2049
2050 __all__.append('SrcUploader')
2051
2052 ################################################################################
2053
2054 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2055                  ('SuiteID', 'suite_id'),
2056                  ('Version', 'version'),
2057                  ('Origin', 'origin'),
2058                  ('Label', 'label'),
2059                  ('Description', 'description'),
2060                  ('Untouchable', 'untouchable'),
2061                  ('Announce', 'announce'),
2062                  ('Codename', 'codename'),
2063                  ('OverrideCodename', 'overridecodename'),
2064                  ('ValidTime', 'validtime'),
2065                  ('Priority', 'priority'),
2066                  ('NotAutomatic', 'notautomatic'),
2067                  ('CopyChanges', 'copychanges'),
2068                  ('CopyDotDak', 'copydotdak'),
2069                  ('CommentsDir', 'commentsdir'),
2070                  ('OverrideSuite', 'overridesuite'),
2071                  ('ChangelogBase', 'changelogbase')]
2072
2073
2074 class Suite(object):
2075     def __init__(self, *args, **kwargs):
2076         pass
2077
2078     def __repr__(self):
2079         return '<Suite %s>' % self.suite_name
2080
2081     def __eq__(self, val):
2082         if isinstance(val, str):
2083             return (self.suite_name == val)
2084         # This signals to use the normal comparison operator
2085         return NotImplemented
2086
2087     def __ne__(self, val):
2088         if isinstance(val, str):
2089             return (self.suite_name != val)
2090         # This signals to use the normal comparison operator
2091         return NotImplemented
2092
2093     def details(self):
2094         ret = []
2095         for disp, field in SUITE_FIELDS:
2096             val = getattr(self, field, None)
2097             if val is not None:
2098                 ret.append("%s: %s" % (disp, val))
2099
2100         return "\n".join(ret)
2101
2102 __all__.append('Suite')
2103
2104 def get_suite_architecture(suite, architecture, session=None):
2105     """
2106     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2107     doesn't exist
2108
2109     @type suite: str
2110     @param suite: Suite name to search for
2111
2112     @type architecture: str
2113     @param architecture: Architecture name to search for
2114
2115     @type session: Session
2116     @param session: Optional SQL session object (a temporary one will be
2117     generated if not supplied)
2118
2119     @rtype: SuiteArchitecture
2120     @return: the SuiteArchitecture object or None
2121     """
2122
2123     privatetrans = False
2124     if session is None:
2125         session = DBConn().session()
2126         privatetrans = True
2127
2128     q = session.query(SuiteArchitecture)
2129     q = q.join(Architecture).filter_by(arch_string=architecture)
2130     q = q.join(Suite).filter_by(suite_name=suite)
2131
2132     if q.count() == 0:
2133         ret = None
2134     else:
2135         ret = q.one()
2136
2137     if privatetrans:
2138         session.close()
2139
2140     return ret
2141
2142 __all__.append('get_suite_architecture')
2143
2144 def get_suite(suite, session=None):
2145     """
2146     Returns Suite object for given C{suite name}.
2147
2148     @type suite: string
2149     @param suite: The name of the suite
2150
2151     @type session: Session
2152     @param session: Optional SQLA session object (a temporary one will be
2153     generated if not supplied)
2154
2155     @rtype: Suite
2156     @return: Suite object for the requested suite name (None if not presenT)
2157
2158     """
2159     privatetrans = False
2160     if session is None:
2161         session = DBConn().session()
2162         privatetrans = True
2163
2164     q = session.query(Suite).filter_by(suite_name=suite)
2165
2166     if q.count() == 0:
2167         ret = None
2168     else:
2169         ret = q.one()
2170
2171     if privatetrans:
2172         session.close()
2173
2174     return ret
2175
2176 __all__.append('get_suite')
2177
2178 ################################################################################
2179
2180 class SuiteArchitecture(object):
2181     def __init__(self, *args, **kwargs):
2182         pass
2183
2184     def __repr__(self):
2185         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2186
2187 __all__.append('SuiteArchitecture')
2188
2189 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2190     """
2191     Returns list of Architecture objects for given C{suite} name
2192
2193     @type source: str
2194     @param source: Suite name to search for
2195
2196     @type skipsrc: boolean
2197     @param skipsrc: Whether to skip returning the 'source' architecture entry
2198     (Default False)
2199
2200     @type skipall: boolean
2201     @param skipall: Whether to skip returning the 'all' architecture entry
2202     (Default False)
2203
2204     @type session: Session
2205     @param session: Optional SQL session object (a temporary one will be
2206     generated if not supplied)
2207
2208     @rtype: list
2209     @return: list of Architecture objects for the given name (may be empty)
2210     """
2211
2212     privatetrans = False
2213     if session is None:
2214         session = DBConn().session()
2215         privatetrans = True
2216
2217     q = session.query(Architecture)
2218     q = q.join(SuiteArchitecture)
2219     q = q.join(Suite).filter_by(suite_name=suite)
2220
2221     if skipsrc:
2222         q = q.filter(Architecture.arch_string != 'source')
2223
2224     if skipall:
2225         q = q.filter(Architecture.arch_string != 'all')
2226
2227     q = q.order_by('arch_string')
2228
2229     ret = q.all()
2230
2231     if privatetrans:
2232         session.close()
2233
2234     return ret
2235
2236 __all__.append('get_suite_architectures')
2237
2238 ################################################################################
2239
2240 class SuiteSrcFormat(object):
2241     def __init__(self, *args, **kwargs):
2242         pass
2243
2244     def __repr__(self):
2245         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2246
2247 __all__.append('SuiteSrcFormat')
2248
2249 def get_suite_src_formats(suite, session=None):
2250     """
2251     Returns list of allowed SrcFormat for C{suite}.
2252
2253     @type suite: str
2254     @param suite: Suite name to search for
2255
2256     @type session: Session
2257     @param session: Optional SQL session object (a temporary one will be
2258     generated if not supplied)
2259
2260     @rtype: list
2261     @return: the list of allowed source formats for I{suite}
2262     """
2263
2264     privatetrans = False
2265     if session is None:
2266         session = DBConn().session()
2267         privatetrans = True
2268
2269     q = session.query(SrcFormat)
2270     q = q.join(SuiteSrcFormat)
2271     q = q.join(Suite).filter_by(suite_name=suite)
2272     q = q.order_by('format_name')
2273
2274     ret = q.all()
2275
2276     if privatetrans:
2277         session.close()
2278
2279     return ret
2280
2281 __all__.append('get_suite_src_formats')
2282
2283 ################################################################################
2284
2285 class Uid(object):
2286     def __init__(self, *args, **kwargs):
2287         pass
2288
2289     def __eq__(self, val):
2290         if isinstance(val, str):
2291             return (self.uid == val)
2292         # This signals to use the normal comparison operator
2293         return NotImplemented
2294
2295     def __ne__(self, val):
2296         if isinstance(val, str):
2297             return (self.uid != val)
2298         # This signals to use the normal comparison operator
2299         return NotImplemented
2300
2301     def __repr__(self):
2302         return '<Uid %s (%s)>' % (self.uid, self.name)
2303
2304 __all__.append('Uid')
2305
2306 def add_database_user(uidname, session=None):
2307     """
2308     Adds a database user
2309
2310     @type uidname: string
2311     @param uidname: The uid of the user to add
2312
2313     @type session: SQLAlchemy
2314     @param session: Optional SQL session object (a temporary one will be
2315     generated if not supplied).  If not passed, a commit will be performed at
2316     the end of the function, otherwise the caller is responsible for commiting.
2317
2318     @rtype: Uid
2319     @return: the uid object for the given uidname
2320     """
2321
2322     privatetrans = False
2323     if session is None:
2324         session = DBConn().session()
2325         privatetrans = True
2326
2327     session.execute("CREATE USER :uid", {'uid': uidname})
2328
2329     if privatetrans:
2330         session.commit()
2331         session.close()
2332
2333 __all__.append('add_database_user')
2334
2335 def get_or_set_uid(uidname, session=None):
2336     """
2337     Returns uid object for given uidname.
2338
2339     If no matching uidname is found, a row is inserted.
2340
2341     @type uidname: string
2342     @param uidname: The uid to add
2343
2344     @type session: SQLAlchemy
2345     @param session: Optional SQL session object (a temporary one will be
2346     generated if not supplied).  If not passed, a commit will be performed at
2347     the end of the function, otherwise the caller is responsible for commiting.
2348
2349     @rtype: Uid
2350     @return: the uid object for the given uidname
2351     """
2352
2353     privatetrans = False
2354     if session is None:
2355         session = DBConn().session()
2356         privatetrans = True
2357
2358     q = session.query(Uid).filter_by(uid=uidname)
2359
2360     if q.count() < 1:
2361         uid = Uid()
2362         uid.uid = uidname
2363         session.add(uid)
2364         if privatetrans:
2365             session.commit()
2366         else:
2367             session.flush()
2368         ret = uid
2369     else:
2370         ret = q.one()
2371
2372     if privatetrans:
2373         session.close()
2374
2375     return ret
2376
2377 __all__.append('get_or_set_uid')
2378
2379
2380 def get_uid_from_fingerprint(fpr, session=None):
2381     privatetrans = False
2382     if session is None:
2383         session = DBConn().session()
2384         privatetrans = True
2385
2386     q = session.query(Uid)
2387     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2388
2389     if q.count() != 1:
2390         ret = None
2391     else:
2392         ret = q.one()
2393
2394     if privatetrans:
2395         session.close()
2396
2397     return ret
2398
2399 __all__.append('get_uid_from_fingerprint')
2400
2401 ################################################################################
2402
2403 class DBConn(Singleton):
2404     """
2405     database module init.
2406     """
2407     def __init__(self, *args, **kwargs):
2408         super(DBConn, self).__init__(*args, **kwargs)
2409
2410     def _startup(self, *args, **kwargs):
2411         self.debug = False
2412         if kwargs.has_key('debug'):
2413             self.debug = True
2414         self.__createconn()
2415
2416     def __setuptables(self):
2417         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2418         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2419         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2420         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2421         self.tbl_component = Table('component', self.db_meta, autoload=True)
2422         self.tbl_config = Table('config', self.db_meta, autoload=True)
2423         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2424         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2425         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2426         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2427         self.tbl_files = Table('files', self.db_meta, autoload=True)
2428         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2429         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2430         self.tbl_location = Table('location', self.db_meta, autoload=True)
2431         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2432         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2433         self.tbl_override = Table('override', self.db_meta, autoload=True)
2434         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2435         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2436         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2437         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2438         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2439         self.tbl_section = Table('section', self.db_meta, autoload=True)
2440         self.tbl_source = Table('source', self.db_meta, autoload=True)
2441         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2442         self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2443         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2444         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2445         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2446         self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2447         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2448
2449     def __setupmappers(self):
2450         mapper(Architecture, self.tbl_architecture,
2451                properties = dict(arch_id = self.tbl_architecture.c.id))
2452
2453         mapper(Archive, self.tbl_archive,
2454                properties = dict(archive_id = self.tbl_archive.c.id,
2455                                  archive_name = self.tbl_archive.c.name))
2456
2457         mapper(BinAssociation, self.tbl_bin_associations,
2458                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2459                                  suite_id = self.tbl_bin_associations.c.suite,
2460                                  suite = relation(Suite),
2461                                  binary_id = self.tbl_bin_associations.c.bin,
2462                                  binary = relation(DBBinary)))
2463
2464         mapper(DBBinary, self.tbl_binaries,
2465                properties = dict(binary_id = self.tbl_binaries.c.id,
2466                                  package = self.tbl_binaries.c.package,
2467                                  version = self.tbl_binaries.c.version,
2468                                  maintainer_id = self.tbl_binaries.c.maintainer,
2469                                  maintainer = relation(Maintainer),
2470                                  source_id = self.tbl_binaries.c.source,
2471                                  source = relation(DBSource),
2472                                  arch_id = self.tbl_binaries.c.architecture,
2473                                  architecture = relation(Architecture),
2474                                  poolfile_id = self.tbl_binaries.c.file,
2475                                  poolfile = relation(PoolFile),
2476                                  binarytype = self.tbl_binaries.c.type,
2477                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2478                                  fingerprint = relation(Fingerprint),
2479                                  install_date = self.tbl_binaries.c.install_date,
2480                                  binassociations = relation(BinAssociation,
2481                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2482
2483         mapper(Component, self.tbl_component,
2484                properties = dict(component_id = self.tbl_component.c.id,
2485                                  component_name = self.tbl_component.c.name))
2486
2487         mapper(DBConfig, self.tbl_config,
2488                properties = dict(config_id = self.tbl_config.c.id))
2489
2490         mapper(ContentAssociation, self.tbl_content_associations,
2491                properties = dict(ca_id = self.tbl_content_associations.c.id,
2492                                  filename_id = self.tbl_content_associations.c.filename,
2493                                  filename    = relation(ContentFilename),
2494                                  filepath_id = self.tbl_content_associations.c.filepath,
2495                                  filepath    = relation(ContentFilepath),
2496                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
2497                                  binary      = relation(DBBinary)))
2498
2499
2500         mapper(ContentFilename, self.tbl_content_file_names,
2501                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2502                                  filename = self.tbl_content_file_names.c.file))
2503
2504         mapper(ContentFilepath, self.tbl_content_file_paths,
2505                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2506                                  filepath = self.tbl_content_file_paths.c.path))
2507
2508         mapper(DSCFile, self.tbl_dsc_files,
2509                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2510                                  source_id = self.tbl_dsc_files.c.source,
2511                                  source = relation(DBSource),
2512                                  poolfile_id = self.tbl_dsc_files.c.file,
2513                                  poolfile = relation(PoolFile)))
2514
2515         mapper(PoolFile, self.tbl_files,
2516                properties = dict(file_id = self.tbl_files.c.id,
2517                                  filesize = self.tbl_files.c.size,
2518                                  location_id = self.tbl_files.c.location,
2519                                  location = relation(Location)))
2520
2521         mapper(Fingerprint, self.tbl_fingerprint,
2522                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2523                                  uid_id = self.tbl_fingerprint.c.uid,
2524                                  uid = relation(Uid),
2525                                  keyring_id = self.tbl_fingerprint.c.keyring,
2526                                  keyring = relation(Keyring)))
2527
2528         mapper(Keyring, self.tbl_keyrings,
2529                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2530                                  keyring_id = self.tbl_keyrings.c.id))
2531
2532         mapper(Location, self.tbl_location,
2533                properties = dict(location_id = self.tbl_location.c.id,
2534                                  component_id = self.tbl_location.c.component,
2535                                  component = relation(Component),
2536                                  archive_id = self.tbl_location.c.archive,
2537                                  archive = relation(Archive),
2538                                  archive_type = self.tbl_location.c.type))
2539
2540         mapper(Maintainer, self.tbl_maintainer,
2541                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2542
2543         mapper(NewComment, self.tbl_new_comments,
2544                properties = dict(comment_id = self.tbl_new_comments.c.id))
2545
2546         mapper(Override, self.tbl_override,
2547                properties = dict(suite_id = self.tbl_override.c.suite,
2548                                  suite = relation(Suite),
2549                                  component_id = self.tbl_override.c.component,
2550                                  component = relation(Component),
2551                                  priority_id = self.tbl_override.c.priority,
2552                                  priority = relation(Priority),
2553                                  section_id = self.tbl_override.c.section,
2554                                  section = relation(Section),
2555                                  overridetype_id = self.tbl_override.c.type,
2556                                  overridetype = relation(OverrideType)))
2557
2558         mapper(OverrideType, self.tbl_override_type,
2559                properties = dict(overridetype = self.tbl_override_type.c.type,
2560                                  overridetype_id = self.tbl_override_type.c.id))
2561
2562         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2563                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2564                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
2565                                  filepath = relation(ContentFilepath),
2566                                  filename_id = self.tbl_pending_content_associations.c.filename,
2567                                  filename = relation(ContentFilename)))
2568
2569         mapper(Priority, self.tbl_priority,
2570                properties = dict(priority_id = self.tbl_priority.c.id))
2571
2572         mapper(Queue, self.tbl_queue,
2573                properties = dict(queue_id = self.tbl_queue.c.id))
2574
2575         mapper(QueueBuild, self.tbl_queue_build,
2576                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2577                                  queue_id = self.tbl_queue_build.c.queue,
2578                                  queue = relation(Queue, backref='queuebuild')))
2579
2580         mapper(Section, self.tbl_section,
2581                properties = dict(section_id = self.tbl_section.c.id))
2582
2583         mapper(DBSource, self.tbl_source,
2584                properties = dict(source_id = self.tbl_source.c.id,
2585                                  version = self.tbl_source.c.version,
2586                                  maintainer_id = self.tbl_source.c.maintainer,
2587                                  maintainer = relation(Maintainer,
2588                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2589                                  poolfile_id = self.tbl_source.c.file,
2590                                  poolfile = relation(PoolFile),
2591                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2592                                  fingerprint = relation(Fingerprint),
2593                                  changedby_id = self.tbl_source.c.changedby,
2594                                  changedby = relation(Maintainer,
2595                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2596                                  srcfiles = relation(DSCFile,
2597                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2598                                  srcassociations = relation(SrcAssociation,
2599                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2600
2601         mapper(SrcAssociation, self.tbl_src_associations,
2602                properties = dict(sa_id = self.tbl_src_associations.c.id,
2603                                  suite_id = self.tbl_src_associations.c.suite,
2604                                  suite = relation(Suite),
2605                                  source_id = self.tbl_src_associations.c.source,
2606                                  source = relation(DBSource)))
2607
2608         mapper(SrcFormat, self.tbl_src_format,
2609                properties = dict(src_format_id = self.tbl_src_format.c.id,
2610                                  format_name = self.tbl_src_format.c.format_name))
2611
2612         mapper(SrcUploader, self.tbl_src_uploaders,
2613                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2614                                  source_id = self.tbl_src_uploaders.c.source,
2615                                  source = relation(DBSource,
2616                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2617                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2618                                  maintainer = relation(Maintainer,
2619                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2620
2621         mapper(Suite, self.tbl_suite,
2622                properties = dict(suite_id = self.tbl_suite.c.id))
2623
2624         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2625                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2626                                  suite = relation(Suite, backref='suitearchitectures'),
2627                                  arch_id = self.tbl_suite_architectures.c.architecture,
2628                                  architecture = relation(Architecture)))
2629
2630         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2631                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2632                                  suite = relation(Suite, backref='suitesrcformats'),
2633                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
2634                                  src_format = relation(SrcFormat)))
2635
2636         mapper(Uid, self.tbl_uid,
2637                properties = dict(uid_id = self.tbl_uid.c.id,
2638                                  fingerprint = relation(Fingerprint)))
2639
2640     ## Connection functions
2641     def __createconn(self):
2642         from config import Config
2643         cnf = Config()
2644         if cnf["DB::Host"]:
2645             # TCP/IP
2646             connstr = "postgres://%s" % cnf["DB::Host"]
2647             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2648                 connstr += ":%s" % cnf["DB::Port"]
2649             connstr += "/%s" % cnf["DB::Name"]
2650         else:
2651             # Unix Socket
2652             connstr = "postgres:///%s" % cnf["DB::Name"]
2653             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2654                 connstr += "?port=%s" % cnf["DB::Port"]
2655
2656         self.db_pg   = create_engine(connstr, echo=self.debug)
2657         self.db_meta = MetaData()
2658         self.db_meta.bind = self.db_pg
2659         self.db_smaker = sessionmaker(bind=self.db_pg,
2660                                       autoflush=True,
2661                                       autocommit=False)
2662
2663         self.__setuptables()
2664         self.__setupmappers()
2665
2666     def session(self):
2667         return self.db_smaker()
2668
2669 __all__.append('DBConn')
2670
2671