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