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