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