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