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