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