]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
Test and refactor get_suites_source_in().
[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, 2010  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 commands
41 from datetime import datetime, timedelta
42 from errno import ENOENT
43 from tempfile import mkstemp, mkdtemp
44
45 from inspect import getargspec
46
47 import sqlalchemy
48 from sqlalchemy import create_engine, Table, MetaData, Column, Integer
49 from sqlalchemy.orm import sessionmaker, mapper, relation, object_session, backref
50 from sqlalchemy import types as sqltypes
51
52 # Don't remove this, we re-export the exceptions to scripts which import us
53 from sqlalchemy.exc import *
54 from sqlalchemy.orm.exc import NoResultFound
55
56 # Only import Config until Queue stuff is changed to store its config
57 # in the database
58 from config import Config
59 from textutils import fix_maintainer
60 from dak_exceptions import NoSourceFieldError
61
62 # suppress some deprecation warnings in squeeze related to sqlalchemy
63 import warnings
64 warnings.filterwarnings('ignore', \
65     "The SQLAlchemy PostgreSQL dialect has been renamed from 'postgres' to 'postgresql'.*", \
66     SADeprecationWarning)
67 # TODO: sqlalchemy needs some extra configuration to correctly reflect
68 # the ind_deb_contents_* indexes - we ignore the warnings at the moment
69 warnings.filterwarnings("ignore", 'Predicate of partial index', SAWarning)
70
71
72 ################################################################################
73
74 # Patch in support for the debversion field type so that it works during
75 # reflection
76
77 try:
78     # that is for sqlalchemy 0.6
79     UserDefinedType = sqltypes.UserDefinedType
80 except:
81     # this one for sqlalchemy 0.5
82     UserDefinedType = sqltypes.TypeEngine
83
84 class DebVersion(UserDefinedType):
85     def get_col_spec(self):
86         return "DEBVERSION"
87
88     def bind_processor(self, dialect):
89         return None
90
91     # ' = None' is needed for sqlalchemy 0.5:
92     def result_processor(self, dialect, coltype = None):
93         return None
94
95 sa_major_version = sqlalchemy.__version__[0:3]
96 if sa_major_version in ["0.5", "0.6"]:
97     from sqlalchemy.databases import postgres
98     postgres.ischema_names['debversion'] = DebVersion
99 else:
100     raise Exception("dak only ported to SQLA versions 0.5 and 0.6.  See daklib/dbconn.py")
101
102 ################################################################################
103
104 __all__ = ['IntegrityError', 'SQLAlchemyError', 'DebVersion']
105
106 ################################################################################
107
108 def session_wrapper(fn):
109     """
110     Wrapper around common ".., session=None):" handling. If the wrapped
111     function is called without passing 'session', we create a local one
112     and destroy it when the function ends.
113
114     Also attaches a commit_or_flush method to the session; if we created a
115     local session, this is a synonym for session.commit(), otherwise it is a
116     synonym for session.flush().
117     """
118
119     def wrapped(*args, **kwargs):
120         private_transaction = False
121
122         # Find the session object
123         session = kwargs.get('session')
124
125         if session is None:
126             if len(args) <= len(getargspec(fn)[0]) - 1:
127                 # No session specified as last argument or in kwargs
128                 private_transaction = True
129                 session = kwargs['session'] = DBConn().session()
130             else:
131                 # Session is last argument in args
132                 session = args[-1]
133                 if session is None:
134                     args = list(args)
135                     session = args[-1] = DBConn().session()
136                     private_transaction = True
137
138         if private_transaction:
139             session.commit_or_flush = session.commit
140         else:
141             session.commit_or_flush = session.flush
142
143         try:
144             return fn(*args, **kwargs)
145         finally:
146             if private_transaction:
147                 # We created a session; close it.
148                 session.close()
149
150     wrapped.__doc__ = fn.__doc__
151     wrapped.func_name = fn.func_name
152
153     return wrapped
154
155 __all__.append('session_wrapper')
156
157 ################################################################################
158
159 class Architecture(object):
160     def __init__(self, arch_string = None, description = None):
161         self.arch_string = arch_string
162         self.description = description
163
164     def __eq__(self, val):
165         if isinstance(val, str):
166             return (self.arch_string== val)
167         # This signals to use the normal comparison operator
168         return NotImplemented
169
170     def __ne__(self, val):
171         if isinstance(val, str):
172             return (self.arch_string != val)
173         # This signals to use the normal comparison operator
174         return NotImplemented
175
176     def __repr__(self):
177         return '<Architecture %s>' % self.arch_string
178
179 __all__.append('Architecture')
180
181 @session_wrapper
182 def get_architecture(architecture, session=None):
183     """
184     Returns database id for given C{architecture}.
185
186     @type architecture: string
187     @param architecture: The name of the architecture
188
189     @type session: Session
190     @param session: Optional SQLA session object (a temporary one will be
191     generated if not supplied)
192
193     @rtype: Architecture
194     @return: Architecture object for the given arch (None if not present)
195     """
196
197     q = session.query(Architecture).filter_by(arch_string=architecture)
198
199     try:
200         return q.one()
201     except NoResultFound:
202         return None
203
204 __all__.append('get_architecture')
205
206 # TODO: should be removed because the implementation is too trivial
207 @session_wrapper
208 def get_architecture_suites(architecture, session=None):
209     """
210     Returns list of Suite objects for given C{architecture} name
211
212     @type architecture: str
213     @param architecture: Architecture name to search for
214
215     @type session: Session
216     @param session: Optional SQL session object (a temporary one will be
217     generated if not supplied)
218
219     @rtype: list
220     @return: list of Suite objects for the given name (may be empty)
221     """
222
223     return get_architecture(architecture, session).suites
224
225 __all__.append('get_architecture_suites')
226
227 ################################################################################
228
229 class Archive(object):
230     def __init__(self, *args, **kwargs):
231         pass
232
233     def __repr__(self):
234         return '<Archive %s>' % self.archive_name
235
236 __all__.append('Archive')
237
238 @session_wrapper
239 def get_archive(archive, session=None):
240     """
241     returns database id for given C{archive}.
242
243     @type archive: string
244     @param archive: the name of the arhive
245
246     @type session: Session
247     @param session: Optional SQLA session object (a temporary one will be
248     generated if not supplied)
249
250     @rtype: Archive
251     @return: Archive object for the given name (None if not present)
252
253     """
254     archive = archive.lower()
255
256     q = session.query(Archive).filter_by(archive_name=archive)
257
258     try:
259         return q.one()
260     except NoResultFound:
261         return None
262
263 __all__.append('get_archive')
264
265 ################################################################################
266
267 class BinAssociation(object):
268     def __init__(self, *args, **kwargs):
269         pass
270
271     def __repr__(self):
272         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
273
274 __all__.append('BinAssociation')
275
276 ################################################################################
277
278 class BinContents(object):
279     def __init__(self, *args, **kwargs):
280         pass
281
282     def __repr__(self):
283         return '<BinContents (%s, %s)>' % (self.binary, self.filename)
284
285 __all__.append('BinContents')
286
287 ################################################################################
288
289 class DBBinary(object):
290     def __init__(self, *args, **kwargs):
291         pass
292
293     def __repr__(self):
294         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
295
296 __all__.append('DBBinary')
297
298 @session_wrapper
299 def get_suites_binary_in(package, session=None):
300     """
301     Returns list of Suite objects which given C{package} name is in
302
303     @type package: str
304     @param package: DBBinary package name to search for
305
306     @rtype: list
307     @return: list of Suite objects for the given package
308     """
309
310     return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
311
312 __all__.append('get_suites_binary_in')
313
314 @session_wrapper
315 def get_binary_from_id(binary_id, session=None):
316     """
317     Returns DBBinary object for given C{id}
318
319     @type binary_id: int
320     @param binary_id: Id of the required binary
321
322     @type session: Session
323     @param session: Optional SQLA session object (a temporary one will be
324     generated if not supplied)
325
326     @rtype: DBBinary
327     @return: DBBinary object for the given binary (None if not present)
328     """
329
330     q = session.query(DBBinary).filter_by(binary_id=binary_id)
331
332     try:
333         return q.one()
334     except NoResultFound:
335         return None
336
337 __all__.append('get_binary_from_id')
338
339 @session_wrapper
340 def get_binaries_from_name(package, version=None, architecture=None, session=None):
341     """
342     Returns list of DBBinary objects for given C{package} name
343
344     @type package: str
345     @param package: DBBinary package name to search for
346
347     @type version: str or None
348     @param version: Version to search for (or None)
349
350     @type architecture: str, list or None
351     @param architecture: Architectures to limit to (or None if no limit)
352
353     @type session: Session
354     @param session: Optional SQL session object (a temporary one will be
355     generated if not supplied)
356
357     @rtype: list
358     @return: list of DBBinary objects for the given name (may be empty)
359     """
360
361     q = session.query(DBBinary).filter_by(package=package)
362
363     if version is not None:
364         q = q.filter_by(version=version)
365
366     if architecture is not None:
367         if not isinstance(architecture, list):
368             architecture = [architecture]
369         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
370
371     ret = q.all()
372
373     return ret
374
375 __all__.append('get_binaries_from_name')
376
377 @session_wrapper
378 def get_binaries_from_source_id(source_id, session=None):
379     """
380     Returns list of DBBinary objects for given C{source_id}
381
382     @type source_id: int
383     @param source_id: source_id to search for
384
385     @type session: Session
386     @param session: Optional SQL session object (a temporary one will be
387     generated if not supplied)
388
389     @rtype: list
390     @return: list of DBBinary objects for the given name (may be empty)
391     """
392
393     return session.query(DBBinary).filter_by(source_id=source_id).all()
394
395 __all__.append('get_binaries_from_source_id')
396
397 @session_wrapper
398 def get_binary_from_name_suite(package, suitename, session=None):
399     ### For dak examine-package
400     ### XXX: Doesn't use object API yet
401
402     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
403              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
404              WHERE b.package='%(package)s'
405                AND b.file = fi.id
406                AND fi.location = l.id
407                AND l.component = c.id
408                AND ba.bin=b.id
409                AND ba.suite = su.id
410                AND su.suite_name %(suitename)s
411           ORDER BY b.version DESC"""
412
413     return session.execute(sql % {'package': package, 'suitename': suitename})
414
415 __all__.append('get_binary_from_name_suite')
416
417 @session_wrapper
418 def get_binary_components(package, suitename, arch, session=None):
419     # Check for packages that have moved from one component to another
420     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
421     WHERE b.package=:package AND s.suite_name=:suitename
422       AND (a.arch_string = :arch OR a.arch_string = 'all')
423       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
424       AND f.location = l.id
425       AND l.component = c.id
426       AND b.file = f.id"""
427
428     vals = {'package': package, 'suitename': suitename, 'arch': arch}
429
430     return session.execute(query, vals)
431
432 __all__.append('get_binary_components')
433
434 ################################################################################
435
436 class BinaryACL(object):
437     def __init__(self, *args, **kwargs):
438         pass
439
440     def __repr__(self):
441         return '<BinaryACL %s>' % self.binary_acl_id
442
443 __all__.append('BinaryACL')
444
445 ################################################################################
446
447 class BinaryACLMap(object):
448     def __init__(self, *args, **kwargs):
449         pass
450
451     def __repr__(self):
452         return '<BinaryACLMap %s>' % self.binary_acl_map_id
453
454 __all__.append('BinaryACLMap')
455
456 ################################################################################
457
458 MINIMAL_APT_CONF="""
459 Dir
460 {
461    ArchiveDir "%(archivepath)s";
462    OverrideDir "%(overridedir)s";
463    CacheDir "%(cachedir)s";
464 };
465
466 Default
467 {
468    Packages::Compress ". bzip2 gzip";
469    Sources::Compress ". bzip2 gzip";
470    DeLinkLimit 0;
471    FileMode 0664;
472 }
473
474 bindirectory "incoming"
475 {
476    Packages "Packages";
477    Contents " ";
478
479    BinOverride "override.sid.all3";
480    BinCacheDB "packages-accepted.db";
481
482    FileList "%(filelist)s";
483
484    PathPrefix "";
485    Packages::Extensions ".deb .udeb";
486 };
487
488 bindirectory "incoming/"
489 {
490    Sources "Sources";
491    BinOverride "override.sid.all3";
492    SrcOverride "override.sid.all3.src";
493    FileList "%(filelist)s";
494 };
495 """
496
497 class BuildQueue(object):
498     def __init__(self, *args, **kwargs):
499         pass
500
501     def __repr__(self):
502         return '<BuildQueue %s>' % self.queue_name
503
504     def write_metadata(self, starttime, force=False):
505         # Do we write out metafiles?
506         if not (force or self.generate_metadata):
507             return
508
509         session = DBConn().session().object_session(self)
510
511         fl_fd = fl_name = ac_fd = ac_name = None
512         tempdir = None
513         arches = " ".join([ a.arch_string for a in session.query(Architecture).all() if a.arch_string != 'source' ])
514         startdir = os.getcwd()
515
516         try:
517             # Grab files we want to include
518             newer = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) > starttime).all()
519             # Write file list with newer files
520             (fl_fd, fl_name) = mkstemp()
521             for n in newer:
522                 os.write(fl_fd, '%s\n' % n.fullpath)
523             os.close(fl_fd)
524
525             cnf = Config()
526
527             # Write minimal apt.conf
528             # TODO: Remove hardcoding from template
529             (ac_fd, ac_name) = mkstemp()
530             os.write(ac_fd, MINIMAL_APT_CONF % {'archivepath': self.path,
531                                                 'filelist': fl_name,
532                                                 'cachedir': cnf["Dir::Cache"],
533                                                 'overridedir': cnf["Dir::Override"],
534                                                 })
535             os.close(ac_fd)
536
537             # Run apt-ftparchive generate
538             os.chdir(os.path.dirname(ac_name))
539             os.system('apt-ftparchive -qq -o APT::FTPArchive::Contents=off generate %s' % os.path.basename(ac_name))
540
541             # Run apt-ftparchive release
542             # TODO: Eww - fix this
543             bname = os.path.basename(self.path)
544             os.chdir(self.path)
545             os.chdir('..')
546
547             # We have to remove the Release file otherwise it'll be included in the
548             # new one
549             try:
550                 os.unlink(os.path.join(bname, 'Release'))
551             except OSError:
552                 pass
553
554             os.system("""apt-ftparchive -qq -o APT::FTPArchive::Release::Origin="%s" -o APT::FTPArchive::Release::Label="%s" -o APT::FTPArchive::Release::Description="%s" -o APT::FTPArchive::Release::Architectures="%s" release %s > Release""" % (self.origin, self.label, self.releasedescription, arches, bname))
555
556             # Crude hack with open and append, but this whole section is and should be redone.
557             if self.notautomatic:
558                 release=open("Release", "a")
559                 release.write("NotAutomatic: yes")
560                 release.close()
561
562             # Sign if necessary
563             if self.signingkey:
564                 keyring = "--secret-keyring \"%s\"" % cnf["Dinstall::SigningKeyring"]
565                 if cnf.has_key("Dinstall::SigningPubKeyring"):
566                     keyring += " --keyring \"%s\"" % cnf["Dinstall::SigningPubKeyring"]
567
568                 os.system("gpg %s --no-options --batch --no-tty --armour --default-key %s --detach-sign -o Release.gpg Release""" % (keyring, self.signingkey))
569
570             # Move the files if we got this far
571             os.rename('Release', os.path.join(bname, 'Release'))
572             if self.signingkey:
573                 os.rename('Release.gpg', os.path.join(bname, 'Release.gpg'))
574
575         # Clean up any left behind files
576         finally:
577             os.chdir(startdir)
578             if fl_fd:
579                 try:
580                     os.close(fl_fd)
581                 except OSError:
582                     pass
583
584             if fl_name:
585                 try:
586                     os.unlink(fl_name)
587                 except OSError:
588                     pass
589
590             if ac_fd:
591                 try:
592                     os.close(ac_fd)
593                 except OSError:
594                     pass
595
596             if ac_name:
597                 try:
598                     os.unlink(ac_name)
599                 except OSError:
600                     pass
601
602     def clean_and_update(self, starttime, Logger, dryrun=False):
603         """WARNING: This routine commits for you"""
604         session = DBConn().session().object_session(self)
605
606         if self.generate_metadata and not dryrun:
607             self.write_metadata(starttime)
608
609         # Grab files older than our execution time
610         older = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) <= starttime).all()
611
612         for o in older:
613             killdb = False
614             try:
615                 if dryrun:
616                     Logger.log(["I: Would have removed %s from the queue" % o.fullpath])
617                 else:
618                     Logger.log(["I: Removing %s from the queue" % o.fullpath])
619                     os.unlink(o.fullpath)
620                     killdb = True
621             except OSError, e:
622                 # If it wasn't there, don't worry
623                 if e.errno == ENOENT:
624                     killdb = True
625                 else:
626                     # TODO: Replace with proper logging call
627                     Logger.log(["E: Could not remove %s" % o.fullpath])
628
629             if killdb:
630                 session.delete(o)
631
632         session.commit()
633
634         for f in os.listdir(self.path):
635             if f.startswith('Packages') or f.startswith('Source') or f.startswith('Release') or f.startswith('advisory'):
636                 continue
637
638             try:
639                 r = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter_by(filename = f).one()
640             except NoResultFound:
641                 fp = os.path.join(self.path, f)
642                 if dryrun:
643                     Logger.log(["I: Would remove unused link %s" % fp])
644                 else:
645                     Logger.log(["I: Removing unused link %s" % fp])
646                     try:
647                         os.unlink(fp)
648                     except OSError:
649                         Logger.log(["E: Failed to unlink unreferenced file %s" % r.fullpath])
650
651     def add_file_from_pool(self, poolfile):
652         """Copies a file into the pool.  Assumes that the PoolFile object is
653         attached to the same SQLAlchemy session as the Queue object is.
654
655         The caller is responsible for committing after calling this function."""
656         poolfile_basename = poolfile.filename[poolfile.filename.rindex(os.sep)+1:]
657
658         # Check if we have a file of this name or this ID already
659         for f in self.queuefiles:
660             if f.fileid is not None and f.fileid == poolfile.file_id or \
661                f.poolfile.filename == poolfile_basename:
662                    # In this case, update the BuildQueueFile entry so we
663                    # don't remove it too early
664                    f.lastused = datetime.now()
665                    DBConn().session().object_session(poolfile).add(f)
666                    return f
667
668         # Prepare BuildQueueFile object
669         qf = BuildQueueFile()
670         qf.build_queue_id = self.queue_id
671         qf.lastused = datetime.now()
672         qf.filename = poolfile_basename
673
674         targetpath = poolfile.fullpath
675         queuepath = os.path.join(self.path, poolfile_basename)
676
677         try:
678             if self.copy_files:
679                 # We need to copy instead of symlink
680                 import utils
681                 utils.copy(targetpath, queuepath)
682                 # NULL in the fileid field implies a copy
683                 qf.fileid = None
684             else:
685                 os.symlink(targetpath, queuepath)
686                 qf.fileid = poolfile.file_id
687         except OSError:
688             return None
689
690         # Get the same session as the PoolFile is using and add the qf to it
691         DBConn().session().object_session(poolfile).add(qf)
692
693         return qf
694
695
696 __all__.append('BuildQueue')
697
698 @session_wrapper
699 def get_build_queue(queuename, session=None):
700     """
701     Returns BuildQueue object for given C{queue name}, creating it if it does not
702     exist.
703
704     @type queuename: string
705     @param queuename: The name of the queue
706
707     @type session: Session
708     @param session: Optional SQLA session object (a temporary one will be
709     generated if not supplied)
710
711     @rtype: BuildQueue
712     @return: BuildQueue object for the given queue
713     """
714
715     q = session.query(BuildQueue).filter_by(queue_name=queuename)
716
717     try:
718         return q.one()
719     except NoResultFound:
720         return None
721
722 __all__.append('get_build_queue')
723
724 ################################################################################
725
726 class BuildQueueFile(object):
727     def __init__(self, *args, **kwargs):
728         pass
729
730     def __repr__(self):
731         return '<BuildQueueFile %s (%s)>' % (self.filename, self.build_queue_id)
732
733     @property
734     def fullpath(self):
735         return os.path.join(self.buildqueue.path, self.filename)
736
737
738 __all__.append('BuildQueueFile')
739
740 ################################################################################
741
742 class ChangePendingBinary(object):
743     def __init__(self, *args, **kwargs):
744         pass
745
746     def __repr__(self):
747         return '<ChangePendingBinary %s>' % self.change_pending_binary_id
748
749 __all__.append('ChangePendingBinary')
750
751 ################################################################################
752
753 class ChangePendingFile(object):
754     def __init__(self, *args, **kwargs):
755         pass
756
757     def __repr__(self):
758         return '<ChangePendingFile %s>' % self.change_pending_file_id
759
760 __all__.append('ChangePendingFile')
761
762 ################################################################################
763
764 class ChangePendingSource(object):
765     def __init__(self, *args, **kwargs):
766         pass
767
768     def __repr__(self):
769         return '<ChangePendingSource %s>' % self.change_pending_source_id
770
771 __all__.append('ChangePendingSource')
772
773 ################################################################################
774
775 class Component(object):
776     def __init__(self, *args, **kwargs):
777         pass
778
779     def __eq__(self, val):
780         if isinstance(val, str):
781             return (self.component_name == val)
782         # This signals to use the normal comparison operator
783         return NotImplemented
784
785     def __ne__(self, val):
786         if isinstance(val, str):
787             return (self.component_name != val)
788         # This signals to use the normal comparison operator
789         return NotImplemented
790
791     def __repr__(self):
792         return '<Component %s>' % self.component_name
793
794
795 __all__.append('Component')
796
797 @session_wrapper
798 def get_component(component, session=None):
799     """
800     Returns database id for given C{component}.
801
802     @type component: string
803     @param component: The name of the override type
804
805     @rtype: int
806     @return: the database id for the given component
807
808     """
809     component = component.lower()
810
811     q = session.query(Component).filter_by(component_name=component)
812
813     try:
814         return q.one()
815     except NoResultFound:
816         return None
817
818 __all__.append('get_component')
819
820 ################################################################################
821
822 class DBConfig(object):
823     def __init__(self, *args, **kwargs):
824         pass
825
826     def __repr__(self):
827         return '<DBConfig %s>' % self.name
828
829 __all__.append('DBConfig')
830
831 ################################################################################
832
833 @session_wrapper
834 def get_or_set_contents_file_id(filename, session=None):
835     """
836     Returns database id for given filename.
837
838     If no matching file is found, a row is inserted.
839
840     @type filename: string
841     @param filename: The filename
842     @type session: SQLAlchemy
843     @param session: Optional SQL session object (a temporary one will be
844     generated if not supplied).  If not passed, a commit will be performed at
845     the end of the function, otherwise the caller is responsible for commiting.
846
847     @rtype: int
848     @return: the database id for the given component
849     """
850
851     q = session.query(ContentFilename).filter_by(filename=filename)
852
853     try:
854         ret = q.one().cafilename_id
855     except NoResultFound:
856         cf = ContentFilename()
857         cf.filename = filename
858         session.add(cf)
859         session.commit_or_flush()
860         ret = cf.cafilename_id
861
862     return ret
863
864 __all__.append('get_or_set_contents_file_id')
865
866 @session_wrapper
867 def get_contents(suite, overridetype, section=None, session=None):
868     """
869     Returns contents for a suite / overridetype combination, limiting
870     to a section if not None.
871
872     @type suite: Suite
873     @param suite: Suite object
874
875     @type overridetype: OverrideType
876     @param overridetype: OverrideType object
877
878     @type section: Section
879     @param section: Optional section object to limit results to
880
881     @type session: SQLAlchemy
882     @param session: Optional SQL session object (a temporary one will be
883     generated if not supplied)
884
885     @rtype: ResultsProxy
886     @return: ResultsProxy object set up to return tuples of (filename, section,
887     package, arch_id)
888     """
889
890     # find me all of the contents for a given suite
891     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
892                             s.section,
893                             b.package,
894                             b.architecture
895                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
896                    JOIN content_file_names n ON (c.filename=n.id)
897                    JOIN binaries b ON (b.id=c.binary_pkg)
898                    JOIN override o ON (o.package=b.package)
899                    JOIN section s ON (s.id=o.section)
900                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
901                    AND b.type=:overridetypename"""
902
903     vals = {'suiteid': suite.suite_id,
904             'overridetypeid': overridetype.overridetype_id,
905             'overridetypename': overridetype.overridetype}
906
907     if section is not None:
908         contents_q += " AND s.id = :sectionid"
909         vals['sectionid'] = section.section_id
910
911     contents_q += " ORDER BY fn"
912
913     return session.execute(contents_q, vals)
914
915 __all__.append('get_contents')
916
917 ################################################################################
918
919 class ContentFilepath(object):
920     def __init__(self, *args, **kwargs):
921         pass
922
923     def __repr__(self):
924         return '<ContentFilepath %s>' % self.filepath
925
926 __all__.append('ContentFilepath')
927
928 @session_wrapper
929 def get_or_set_contents_path_id(filepath, session=None):
930     """
931     Returns database id for given path.
932
933     If no matching file is found, a row is inserted.
934
935     @type filepath: string
936     @param filepath: The filepath
937
938     @type session: SQLAlchemy
939     @param session: Optional SQL session object (a temporary one will be
940     generated if not supplied).  If not passed, a commit will be performed at
941     the end of the function, otherwise the caller is responsible for commiting.
942
943     @rtype: int
944     @return: the database id for the given path
945     """
946
947     q = session.query(ContentFilepath).filter_by(filepath=filepath)
948
949     try:
950         ret = q.one().cafilepath_id
951     except NoResultFound:
952         cf = ContentFilepath()
953         cf.filepath = filepath
954         session.add(cf)
955         session.commit_or_flush()
956         ret = cf.cafilepath_id
957
958     return ret
959
960 __all__.append('get_or_set_contents_path_id')
961
962 ################################################################################
963
964 class ContentAssociation(object):
965     def __init__(self, *args, **kwargs):
966         pass
967
968     def __repr__(self):
969         return '<ContentAssociation %s>' % self.ca_id
970
971 __all__.append('ContentAssociation')
972
973 def insert_content_paths(binary_id, fullpaths, session=None):
974     """
975     Make sure given path is associated with given binary id
976
977     @type binary_id: int
978     @param binary_id: the id of the binary
979     @type fullpaths: list
980     @param fullpaths: the list of paths of the file being associated with the binary
981     @type session: SQLAlchemy session
982     @param session: Optional SQLAlchemy session.  If this is passed, the caller
983     is responsible for ensuring a transaction has begun and committing the
984     results or rolling back based on the result code.  If not passed, a commit
985     will be performed at the end of the function, otherwise the caller is
986     responsible for commiting.
987
988     @return: True upon success
989     """
990
991     privatetrans = False
992     if session is None:
993         session = DBConn().session()
994         privatetrans = True
995
996     try:
997         # Insert paths
998         def generate_path_dicts():
999             for fullpath in fullpaths:
1000                 if fullpath.startswith( './' ):
1001                     fullpath = fullpath[2:]
1002
1003                 yield {'filename':fullpath, 'id': binary_id }
1004
1005         for d in generate_path_dicts():
1006             session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )",
1007                          d )
1008
1009         session.commit()
1010         if privatetrans:
1011             session.close()
1012         return True
1013
1014     except:
1015         traceback.print_exc()
1016
1017         # Only rollback if we set up the session ourself
1018         if privatetrans:
1019             session.rollback()
1020             session.close()
1021
1022         return False
1023
1024 __all__.append('insert_content_paths')
1025
1026 ################################################################################
1027
1028 class DSCFile(object):
1029     def __init__(self, *args, **kwargs):
1030         pass
1031
1032     def __repr__(self):
1033         return '<DSCFile %s>' % self.dscfile_id
1034
1035 __all__.append('DSCFile')
1036
1037 @session_wrapper
1038 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
1039     """
1040     Returns a list of DSCFiles which may be empty
1041
1042     @type dscfile_id: int (optional)
1043     @param dscfile_id: the dscfile_id of the DSCFiles to find
1044
1045     @type source_id: int (optional)
1046     @param source_id: the source id related to the DSCFiles to find
1047
1048     @type poolfile_id: int (optional)
1049     @param poolfile_id: the poolfile id related to the DSCFiles to find
1050
1051     @rtype: list
1052     @return: Possibly empty list of DSCFiles
1053     """
1054
1055     q = session.query(DSCFile)
1056
1057     if dscfile_id is not None:
1058         q = q.filter_by(dscfile_id=dscfile_id)
1059
1060     if source_id is not None:
1061         q = q.filter_by(source_id=source_id)
1062
1063     if poolfile_id is not None:
1064         q = q.filter_by(poolfile_id=poolfile_id)
1065
1066     return q.all()
1067
1068 __all__.append('get_dscfiles')
1069
1070 ################################################################################
1071
1072 class PoolFile(object):
1073     def __init__(self, filename = None, location = None, filesize = -1, \
1074         md5sum = None):
1075         self.filename = filename
1076         self.location = location
1077         self.filesize = filesize
1078         self.md5sum = md5sum
1079
1080     def __repr__(self):
1081         return '<PoolFile %s>' % self.filename
1082
1083     @property
1084     def fullpath(self):
1085         return os.path.join(self.location.path, self.filename)
1086
1087     def is_valid(self, filesize = -1, md5sum = None):\
1088         return self.filesize == filesize and self.md5sum == md5sum
1089
1090 __all__.append('PoolFile')
1091
1092 @session_wrapper
1093 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
1094     """
1095     Returns a tuple:
1096     (ValidFileFound [boolean], PoolFile object or None)
1097
1098     @type filename: string
1099     @param filename: the filename of the file to check against the DB
1100
1101     @type filesize: int
1102     @param filesize: the size of the file to check against the DB
1103
1104     @type md5sum: string
1105     @param md5sum: the md5sum of the file to check against the DB
1106
1107     @type location_id: int
1108     @param location_id: the id of the location to look in
1109
1110     @rtype: tuple
1111     @return: Tuple of length 2.
1112                  - If valid pool file found: (C{True}, C{PoolFile object})
1113                  - If valid pool file not found:
1114                      - (C{False}, C{None}) if no file found
1115                      - (C{False}, C{PoolFile object}) if file found with size/md5sum mismatch
1116     """
1117
1118     poolfile = session.query(Location).get(location_id). \
1119         files.filter_by(filename=filename).first()
1120     valid = False
1121     if poolfile and poolfile.is_valid(filesize = filesize, md5sum = md5sum):
1122         valid = True
1123
1124     return (valid, poolfile)
1125
1126 __all__.append('check_poolfile')
1127
1128 # TODO: the implementation can trivially be inlined at the place where the
1129 # function is called
1130 @session_wrapper
1131 def get_poolfile_by_id(file_id, session=None):
1132     """
1133     Returns a PoolFile objects or None for the given id
1134
1135     @type file_id: int
1136     @param file_id: the id of the file to look for
1137
1138     @rtype: PoolFile or None
1139     @return: either the PoolFile object or None
1140     """
1141
1142     return session.query(PoolFile).get(file_id)
1143
1144 __all__.append('get_poolfile_by_id')
1145
1146 @session_wrapper
1147 def get_poolfile_like_name(filename, session=None):
1148     """
1149     Returns an array of PoolFile objects which are like the given name
1150
1151     @type filename: string
1152     @param filename: the filename of the file to check against the DB
1153
1154     @rtype: array
1155     @return: array of PoolFile objects
1156     """
1157
1158     # TODO: There must be a way of properly using bind parameters with %FOO%
1159     q = session.query(PoolFile).filter(PoolFile.filename.like('%%/%s' % filename))
1160
1161     return q.all()
1162
1163 __all__.append('get_poolfile_like_name')
1164
1165 @session_wrapper
1166 def add_poolfile(filename, datadict, location_id, session=None):
1167     """
1168     Add a new file to the pool
1169
1170     @type filename: string
1171     @param filename: filename
1172
1173     @type datadict: dict
1174     @param datadict: dict with needed data
1175
1176     @type location_id: int
1177     @param location_id: database id of the location
1178
1179     @rtype: PoolFile
1180     @return: the PoolFile object created
1181     """
1182     poolfile = PoolFile()
1183     poolfile.filename = filename
1184     poolfile.filesize = datadict["size"]
1185     poolfile.md5sum = datadict["md5sum"]
1186     poolfile.sha1sum = datadict["sha1sum"]
1187     poolfile.sha256sum = datadict["sha256sum"]
1188     poolfile.location_id = location_id
1189
1190     session.add(poolfile)
1191     # Flush to get a file id (NB: This is not a commit)
1192     session.flush()
1193
1194     return poolfile
1195
1196 __all__.append('add_poolfile')
1197
1198 ################################################################################
1199
1200 class Fingerprint(object):
1201     def __init__(self, fingerprint = None):
1202         self.fingerprint = fingerprint
1203
1204     def __repr__(self):
1205         return '<Fingerprint %s>' % self.fingerprint
1206
1207 __all__.append('Fingerprint')
1208
1209 @session_wrapper
1210 def get_fingerprint(fpr, session=None):
1211     """
1212     Returns Fingerprint object for given fpr.
1213
1214     @type fpr: string
1215     @param fpr: The fpr to find / add
1216
1217     @type session: SQLAlchemy
1218     @param session: Optional SQL session object (a temporary one will be
1219     generated if not supplied).
1220
1221     @rtype: Fingerprint
1222     @return: the Fingerprint object for the given fpr or None
1223     """
1224
1225     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1226
1227     try:
1228         ret = q.one()
1229     except NoResultFound:
1230         ret = None
1231
1232     return ret
1233
1234 __all__.append('get_fingerprint')
1235
1236 @session_wrapper
1237 def get_or_set_fingerprint(fpr, session=None):
1238     """
1239     Returns Fingerprint object for given fpr.
1240
1241     If no matching fpr is found, a row is inserted.
1242
1243     @type fpr: string
1244     @param fpr: The fpr to find / add
1245
1246     @type session: SQLAlchemy
1247     @param session: Optional SQL session object (a temporary one will be
1248     generated if not supplied).  If not passed, a commit will be performed at
1249     the end of the function, otherwise the caller is responsible for commiting.
1250     A flush will be performed either way.
1251
1252     @rtype: Fingerprint
1253     @return: the Fingerprint object for the given fpr
1254     """
1255
1256     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1257
1258     try:
1259         ret = q.one()
1260     except NoResultFound:
1261         fingerprint = Fingerprint()
1262         fingerprint.fingerprint = fpr
1263         session.add(fingerprint)
1264         session.commit_or_flush()
1265         ret = fingerprint
1266
1267     return ret
1268
1269 __all__.append('get_or_set_fingerprint')
1270
1271 ################################################################################
1272
1273 # Helper routine for Keyring class
1274 def get_ldap_name(entry):
1275     name = []
1276     for k in ["cn", "mn", "sn"]:
1277         ret = entry.get(k)
1278         if ret and ret[0] != "" and ret[0] != "-":
1279             name.append(ret[0])
1280     return " ".join(name)
1281
1282 ################################################################################
1283
1284 class Keyring(object):
1285     gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
1286                      " --with-colons --fingerprint --fingerprint"
1287
1288     keys = {}
1289     fpr_lookup = {}
1290
1291     def __init__(self, *args, **kwargs):
1292         pass
1293
1294     def __repr__(self):
1295         return '<Keyring %s>' % self.keyring_name
1296
1297     def de_escape_gpg_str(self, txt):
1298         esclist = re.split(r'(\\x..)', txt)
1299         for x in range(1,len(esclist),2):
1300             esclist[x] = "%c" % (int(esclist[x][2:],16))
1301         return "".join(esclist)
1302
1303     def parse_address(self, uid):
1304         """parses uid and returns a tuple of real name and email address"""
1305         import email.Utils
1306         (name, address) = email.Utils.parseaddr(uid)
1307         name = re.sub(r"\s*[(].*[)]", "", name)
1308         name = self.de_escape_gpg_str(name)
1309         if name == "":
1310             name = uid
1311         return (name, address)
1312
1313     def load_keys(self, keyring):
1314         if not self.keyring_id:
1315             raise Exception('Must be initialized with database information')
1316
1317         k = os.popen(self.gpg_invocation % keyring, "r")
1318         key = None
1319         signingkey = False
1320
1321         for line in k.xreadlines():
1322             field = line.split(":")
1323             if field[0] == "pub":
1324                 key = field[4]
1325                 self.keys[key] = {}
1326                 (name, addr) = self.parse_address(field[9])
1327                 if "@" in addr:
1328                     self.keys[key]["email"] = addr
1329                     self.keys[key]["name"] = name
1330                 self.keys[key]["fingerprints"] = []
1331                 signingkey = True
1332             elif key and field[0] == "sub" and len(field) >= 12:
1333                 signingkey = ("s" in field[11])
1334             elif key and field[0] == "uid":
1335                 (name, addr) = self.parse_address(field[9])
1336                 if "email" not in self.keys[key] and "@" in addr:
1337                     self.keys[key]["email"] = addr
1338                     self.keys[key]["name"] = name
1339             elif signingkey and field[0] == "fpr":
1340                 self.keys[key]["fingerprints"].append(field[9])
1341                 self.fpr_lookup[field[9]] = key
1342
1343     def import_users_from_ldap(self, session):
1344         import ldap
1345         cnf = Config()
1346
1347         LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
1348         LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1349
1350         l = ldap.open(LDAPServer)
1351         l.simple_bind_s("","")
1352         Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
1353                "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1354                ["uid", "keyfingerprint", "cn", "mn", "sn"])
1355
1356         ldap_fin_uid_id = {}
1357
1358         byuid = {}
1359         byname = {}
1360
1361         for i in Attrs:
1362             entry = i[1]
1363             uid = entry["uid"][0]
1364             name = get_ldap_name(entry)
1365             fingerprints = entry["keyFingerPrint"]
1366             keyid = None
1367             for f in fingerprints:
1368                 key = self.fpr_lookup.get(f, None)
1369                 if key not in self.keys:
1370                     continue
1371                 self.keys[key]["uid"] = uid
1372
1373                 if keyid != None:
1374                     continue
1375                 keyid = get_or_set_uid(uid, session).uid_id
1376                 byuid[keyid] = (uid, name)
1377                 byname[uid] = (keyid, name)
1378
1379         return (byname, byuid)
1380
1381     def generate_users_from_keyring(self, format, session):
1382         byuid = {}
1383         byname = {}
1384         any_invalid = False
1385         for x in self.keys.keys():
1386             if "email" not in self.keys[x]:
1387                 any_invalid = True
1388                 self.keys[x]["uid"] = format % "invalid-uid"
1389             else:
1390                 uid = format % self.keys[x]["email"]
1391                 keyid = get_or_set_uid(uid, session).uid_id
1392                 byuid[keyid] = (uid, self.keys[x]["name"])
1393                 byname[uid] = (keyid, self.keys[x]["name"])
1394                 self.keys[x]["uid"] = uid
1395
1396         if any_invalid:
1397             uid = format % "invalid-uid"
1398             keyid = get_or_set_uid(uid, session).uid_id
1399             byuid[keyid] = (uid, "ungeneratable user id")
1400             byname[uid] = (keyid, "ungeneratable user id")
1401
1402         return (byname, byuid)
1403
1404 __all__.append('Keyring')
1405
1406 @session_wrapper
1407 def get_keyring(keyring, session=None):
1408     """
1409     If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1410     If C{keyring} already has an entry, simply return the existing Keyring
1411
1412     @type keyring: string
1413     @param keyring: the keyring name
1414
1415     @rtype: Keyring
1416     @return: the Keyring object for this keyring
1417     """
1418
1419     q = session.query(Keyring).filter_by(keyring_name=keyring)
1420
1421     try:
1422         return q.one()
1423     except NoResultFound:
1424         return None
1425
1426 __all__.append('get_keyring')
1427
1428 ################################################################################
1429
1430 class KeyringACLMap(object):
1431     def __init__(self, *args, **kwargs):
1432         pass
1433
1434     def __repr__(self):
1435         return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1436
1437 __all__.append('KeyringACLMap')
1438
1439 ################################################################################
1440
1441 class DBChange(object):
1442     def __init__(self, *args, **kwargs):
1443         pass
1444
1445     def __repr__(self):
1446         return '<DBChange %s>' % self.changesname
1447
1448     def clean_from_queue(self):
1449         session = DBConn().session().object_session(self)
1450
1451         # Remove changes_pool_files entries
1452         self.poolfiles = []
1453
1454         # Remove changes_pending_files references
1455         self.files = []
1456
1457         # Clear out of queue
1458         self.in_queue = None
1459         self.approved_for_id = None
1460
1461 __all__.append('DBChange')
1462
1463 @session_wrapper
1464 def get_dbchange(filename, session=None):
1465     """
1466     returns DBChange object for given C{filename}.
1467
1468     @type filename: string
1469     @param filename: the name of the file
1470
1471     @type session: Session
1472     @param session: Optional SQLA session object (a temporary one will be
1473     generated if not supplied)
1474
1475     @rtype: DBChange
1476     @return:  DBChange object for the given filename (C{None} if not present)
1477
1478     """
1479     q = session.query(DBChange).filter_by(changesname=filename)
1480
1481     try:
1482         return q.one()
1483     except NoResultFound:
1484         return None
1485
1486 __all__.append('get_dbchange')
1487
1488 ################################################################################
1489
1490 class Location(object):
1491     def __init__(self, path = None):
1492         self.path = path
1493         # the column 'type' should go away, see comment at mapper
1494         self.archive_type = 'pool'
1495
1496     def __repr__(self):
1497         return '<Location %s (%s)>' % (self.path, self.location_id)
1498
1499 __all__.append('Location')
1500
1501 @session_wrapper
1502 def get_location(location, component=None, archive=None, session=None):
1503     """
1504     Returns Location object for the given combination of location, component
1505     and archive
1506
1507     @type location: string
1508     @param location: the path of the location, e.g. I{/srv/ftp-master.debian.org/ftp/pool/}
1509
1510     @type component: string
1511     @param component: the component name (if None, no restriction applied)
1512
1513     @type archive: string
1514     @param archive: the archive name (if None, no restriction applied)
1515
1516     @rtype: Location / None
1517     @return: Either a Location object or None if one can't be found
1518     """
1519
1520     q = session.query(Location).filter_by(path=location)
1521
1522     if archive is not None:
1523         q = q.join(Archive).filter_by(archive_name=archive)
1524
1525     if component is not None:
1526         q = q.join(Component).filter_by(component_name=component)
1527
1528     try:
1529         return q.one()
1530     except NoResultFound:
1531         return None
1532
1533 __all__.append('get_location')
1534
1535 ################################################################################
1536
1537 class Maintainer(object):
1538     def __init__(self, name = None):
1539         self.name = name
1540
1541     def __repr__(self):
1542         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1543
1544     def get_split_maintainer(self):
1545         if not hasattr(self, 'name') or self.name is None:
1546             return ('', '', '', '')
1547
1548         return fix_maintainer(self.name.strip())
1549
1550 __all__.append('Maintainer')
1551
1552 @session_wrapper
1553 def get_or_set_maintainer(name, session=None):
1554     """
1555     Returns Maintainer object for given maintainer name.
1556
1557     If no matching maintainer name is found, a row is inserted.
1558
1559     @type name: string
1560     @param name: The maintainer name to add
1561
1562     @type session: SQLAlchemy
1563     @param session: Optional SQL session object (a temporary one will be
1564     generated if not supplied).  If not passed, a commit will be performed at
1565     the end of the function, otherwise the caller is responsible for commiting.
1566     A flush will be performed either way.
1567
1568     @rtype: Maintainer
1569     @return: the Maintainer object for the given maintainer
1570     """
1571
1572     q = session.query(Maintainer).filter_by(name=name)
1573     try:
1574         ret = q.one()
1575     except NoResultFound:
1576         maintainer = Maintainer()
1577         maintainer.name = name
1578         session.add(maintainer)
1579         session.commit_or_flush()
1580         ret = maintainer
1581
1582     return ret
1583
1584 __all__.append('get_or_set_maintainer')
1585
1586 @session_wrapper
1587 def get_maintainer(maintainer_id, session=None):
1588     """
1589     Return the name of the maintainer behind C{maintainer_id} or None if that
1590     maintainer_id is invalid.
1591
1592     @type maintainer_id: int
1593     @param maintainer_id: the id of the maintainer
1594
1595     @rtype: Maintainer
1596     @return: the Maintainer with this C{maintainer_id}
1597     """
1598
1599     return session.query(Maintainer).get(maintainer_id)
1600
1601 __all__.append('get_maintainer')
1602
1603 ################################################################################
1604
1605 class NewComment(object):
1606     def __init__(self, *args, **kwargs):
1607         pass
1608
1609     def __repr__(self):
1610         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1611
1612 __all__.append('NewComment')
1613
1614 @session_wrapper
1615 def has_new_comment(package, version, session=None):
1616     """
1617     Returns true if the given combination of C{package}, C{version} has a comment.
1618
1619     @type package: string
1620     @param package: name of the package
1621
1622     @type version: string
1623     @param version: package version
1624
1625     @type session: Session
1626     @param session: Optional SQLA session object (a temporary one will be
1627     generated if not supplied)
1628
1629     @rtype: boolean
1630     @return: true/false
1631     """
1632
1633     q = session.query(NewComment)
1634     q = q.filter_by(package=package)
1635     q = q.filter_by(version=version)
1636
1637     return bool(q.count() > 0)
1638
1639 __all__.append('has_new_comment')
1640
1641 @session_wrapper
1642 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1643     """
1644     Returns (possibly empty) list of NewComment objects for the given
1645     parameters
1646
1647     @type package: string (optional)
1648     @param package: name of the package
1649
1650     @type version: string (optional)
1651     @param version: package version
1652
1653     @type comment_id: int (optional)
1654     @param comment_id: An id of a comment
1655
1656     @type session: Session
1657     @param session: Optional SQLA session object (a temporary one will be
1658     generated if not supplied)
1659
1660     @rtype: list
1661     @return: A (possibly empty) list of NewComment objects will be returned
1662     """
1663
1664     q = session.query(NewComment)
1665     if package is not None: q = q.filter_by(package=package)
1666     if version is not None: q = q.filter_by(version=version)
1667     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1668
1669     return q.all()
1670
1671 __all__.append('get_new_comments')
1672
1673 ################################################################################
1674
1675 class Override(object):
1676     def __init__(self, *args, **kwargs):
1677         pass
1678
1679     def __repr__(self):
1680         return '<Override %s (%s)>' % (self.package, self.suite_id)
1681
1682 __all__.append('Override')
1683
1684 @session_wrapper
1685 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1686     """
1687     Returns Override object for the given parameters
1688
1689     @type package: string
1690     @param package: The name of the package
1691
1692     @type suite: string, list or None
1693     @param suite: The name of the suite (or suites if a list) to limit to.  If
1694                   None, don't limit.  Defaults to None.
1695
1696     @type component: string, list or None
1697     @param component: The name of the component (or components if a list) to
1698                       limit to.  If None, don't limit.  Defaults to None.
1699
1700     @type overridetype: string, list or None
1701     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1702                          limit to.  If None, don't limit.  Defaults to None.
1703
1704     @type session: Session
1705     @param session: Optional SQLA session object (a temporary one will be
1706     generated if not supplied)
1707
1708     @rtype: list
1709     @return: A (possibly empty) list of Override objects will be returned
1710     """
1711
1712     q = session.query(Override)
1713     q = q.filter_by(package=package)
1714
1715     if suite is not None:
1716         if not isinstance(suite, list): suite = [suite]
1717         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1718
1719     if component is not None:
1720         if not isinstance(component, list): component = [component]
1721         q = q.join(Component).filter(Component.component_name.in_(component))
1722
1723     if overridetype is not None:
1724         if not isinstance(overridetype, list): overridetype = [overridetype]
1725         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1726
1727     return q.all()
1728
1729 __all__.append('get_override')
1730
1731
1732 ################################################################################
1733
1734 class OverrideType(object):
1735     def __init__(self, *args, **kwargs):
1736         pass
1737
1738     def __repr__(self):
1739         return '<OverrideType %s>' % self.overridetype
1740
1741 __all__.append('OverrideType')
1742
1743 @session_wrapper
1744 def get_override_type(override_type, session=None):
1745     """
1746     Returns OverrideType object for given C{override type}.
1747
1748     @type override_type: string
1749     @param override_type: The name of the override type
1750
1751     @type session: Session
1752     @param session: Optional SQLA session object (a temporary one will be
1753     generated if not supplied)
1754
1755     @rtype: int
1756     @return: the database id for the given override type
1757     """
1758
1759     q = session.query(OverrideType).filter_by(overridetype=override_type)
1760
1761     try:
1762         return q.one()
1763     except NoResultFound:
1764         return None
1765
1766 __all__.append('get_override_type')
1767
1768 ################################################################################
1769
1770 class DebContents(object):
1771     def __init__(self, *args, **kwargs):
1772         pass
1773
1774     def __repr__(self):
1775         return '<DebConetnts %s: %s>' % (self.package.package,self.file)
1776
1777 __all__.append('DebContents')
1778
1779
1780 class UdebContents(object):
1781     def __init__(self, *args, **kwargs):
1782         pass
1783
1784     def __repr__(self):
1785         return '<UdebConetnts %s: %s>' % (self.package.package,self.file)
1786
1787 __all__.append('UdebContents')
1788
1789 class PendingBinContents(object):
1790     def __init__(self, *args, **kwargs):
1791         pass
1792
1793     def __repr__(self):
1794         return '<PendingBinContents %s>' % self.contents_id
1795
1796 __all__.append('PendingBinContents')
1797
1798 def insert_pending_content_paths(package,
1799                                  is_udeb,
1800                                  fullpaths,
1801                                  session=None):
1802     """
1803     Make sure given paths are temporarily associated with given
1804     package
1805
1806     @type package: dict
1807     @param package: the package to associate with should have been read in from the binary control file
1808     @type fullpaths: list
1809     @param fullpaths: the list of paths of the file being associated with the binary
1810     @type session: SQLAlchemy session
1811     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1812     is responsible for ensuring a transaction has begun and committing the
1813     results or rolling back based on the result code.  If not passed, a commit
1814     will be performed at the end of the function
1815
1816     @return: True upon success, False if there is a problem
1817     """
1818
1819     privatetrans = False
1820
1821     if session is None:
1822         session = DBConn().session()
1823         privatetrans = True
1824
1825     try:
1826         arch = get_architecture(package['Architecture'], session)
1827         arch_id = arch.arch_id
1828
1829         # Remove any already existing recorded files for this package
1830         q = session.query(PendingBinContents)
1831         q = q.filter_by(package=package['Package'])
1832         q = q.filter_by(version=package['Version'])
1833         q = q.filter_by(architecture=arch_id)
1834         q.delete()
1835
1836         for fullpath in fullpaths:
1837
1838             if fullpath.startswith( "./" ):
1839                 fullpath = fullpath[2:]
1840
1841             pca = PendingBinContents()
1842             pca.package = package['Package']
1843             pca.version = package['Version']
1844             pca.file = fullpath
1845             pca.architecture = arch_id
1846
1847             if isudeb:
1848                 pca.type = 8 # gross
1849             else:
1850                 pca.type = 7 # also gross
1851             session.add(pca)
1852
1853         # Only commit if we set up the session ourself
1854         if privatetrans:
1855             session.commit()
1856             session.close()
1857         else:
1858             session.flush()
1859
1860         return True
1861     except Exception, e:
1862         traceback.print_exc()
1863
1864         # Only rollback if we set up the session ourself
1865         if privatetrans:
1866             session.rollback()
1867             session.close()
1868
1869         return False
1870
1871 __all__.append('insert_pending_content_paths')
1872
1873 ################################################################################
1874
1875 class PolicyQueue(object):
1876     def __init__(self, *args, **kwargs):
1877         pass
1878
1879     def __repr__(self):
1880         return '<PolicyQueue %s>' % self.queue_name
1881
1882 __all__.append('PolicyQueue')
1883
1884 @session_wrapper
1885 def get_policy_queue(queuename, session=None):
1886     """
1887     Returns PolicyQueue object for given C{queue name}
1888
1889     @type queuename: string
1890     @param queuename: The name of the queue
1891
1892     @type session: Session
1893     @param session: Optional SQLA session object (a temporary one will be
1894     generated if not supplied)
1895
1896     @rtype: PolicyQueue
1897     @return: PolicyQueue object for the given queue
1898     """
1899
1900     q = session.query(PolicyQueue).filter_by(queue_name=queuename)
1901
1902     try:
1903         return q.one()
1904     except NoResultFound:
1905         return None
1906
1907 __all__.append('get_policy_queue')
1908
1909 @session_wrapper
1910 def get_policy_queue_from_path(pathname, session=None):
1911     """
1912     Returns PolicyQueue object for given C{path name}
1913
1914     @type queuename: string
1915     @param queuename: The path
1916
1917     @type session: Session
1918     @param session: Optional SQLA session object (a temporary one will be
1919     generated if not supplied)
1920
1921     @rtype: PolicyQueue
1922     @return: PolicyQueue object for the given queue
1923     """
1924
1925     q = session.query(PolicyQueue).filter_by(path=pathname)
1926
1927     try:
1928         return q.one()
1929     except NoResultFound:
1930         return None
1931
1932 __all__.append('get_policy_queue_from_path')
1933
1934 ################################################################################
1935
1936 class Priority(object):
1937     def __init__(self, *args, **kwargs):
1938         pass
1939
1940     def __eq__(self, val):
1941         if isinstance(val, str):
1942             return (self.priority == val)
1943         # This signals to use the normal comparison operator
1944         return NotImplemented
1945
1946     def __ne__(self, val):
1947         if isinstance(val, str):
1948             return (self.priority != val)
1949         # This signals to use the normal comparison operator
1950         return NotImplemented
1951
1952     def __repr__(self):
1953         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1954
1955 __all__.append('Priority')
1956
1957 @session_wrapper
1958 def get_priority(priority, session=None):
1959     """
1960     Returns Priority object for given C{priority name}.
1961
1962     @type priority: string
1963     @param priority: The name of the priority
1964
1965     @type session: Session
1966     @param session: Optional SQLA session object (a temporary one will be
1967     generated if not supplied)
1968
1969     @rtype: Priority
1970     @return: Priority object for the given priority
1971     """
1972
1973     q = session.query(Priority).filter_by(priority=priority)
1974
1975     try:
1976         return q.one()
1977     except NoResultFound:
1978         return None
1979
1980 __all__.append('get_priority')
1981
1982 @session_wrapper
1983 def get_priorities(session=None):
1984     """
1985     Returns dictionary of priority names -> id mappings
1986
1987     @type session: Session
1988     @param session: Optional SQL session object (a temporary one will be
1989     generated if not supplied)
1990
1991     @rtype: dictionary
1992     @return: dictionary of priority names -> id mappings
1993     """
1994
1995     ret = {}
1996     q = session.query(Priority)
1997     for x in q.all():
1998         ret[x.priority] = x.priority_id
1999
2000     return ret
2001
2002 __all__.append('get_priorities')
2003
2004 ################################################################################
2005
2006 class Section(object):
2007     def __init__(self, *args, **kwargs):
2008         pass
2009
2010     def __eq__(self, val):
2011         if isinstance(val, str):
2012             return (self.section == val)
2013         # This signals to use the normal comparison operator
2014         return NotImplemented
2015
2016     def __ne__(self, val):
2017         if isinstance(val, str):
2018             return (self.section != val)
2019         # This signals to use the normal comparison operator
2020         return NotImplemented
2021
2022     def __repr__(self):
2023         return '<Section %s>' % self.section
2024
2025 __all__.append('Section')
2026
2027 @session_wrapper
2028 def get_section(section, session=None):
2029     """
2030     Returns Section object for given C{section name}.
2031
2032     @type section: string
2033     @param section: The name of the section
2034
2035     @type session: Session
2036     @param session: Optional SQLA session object (a temporary one will be
2037     generated if not supplied)
2038
2039     @rtype: Section
2040     @return: Section object for the given section name
2041     """
2042
2043     q = session.query(Section).filter_by(section=section)
2044
2045     try:
2046         return q.one()
2047     except NoResultFound:
2048         return None
2049
2050 __all__.append('get_section')
2051
2052 @session_wrapper
2053 def get_sections(session=None):
2054     """
2055     Returns dictionary of section names -> id mappings
2056
2057     @type session: Session
2058     @param session: Optional SQL session object (a temporary one will be
2059     generated if not supplied)
2060
2061     @rtype: dictionary
2062     @return: dictionary of section names -> id mappings
2063     """
2064
2065     ret = {}
2066     q = session.query(Section)
2067     for x in q.all():
2068         ret[x.section] = x.section_id
2069
2070     return ret
2071
2072 __all__.append('get_sections')
2073
2074 ################################################################################
2075
2076 class DBSource(object):
2077     def __init__(self, source = None, version = None, maintainer = None, \
2078         changedby = None, poolfile = None, install_date = None):
2079         self.source = source
2080         self.version = version
2081         self.maintainer = maintainer
2082         self.changedby = changedby
2083         self.poolfile = poolfile
2084         self.install_date = install_date
2085
2086     def __repr__(self):
2087         return '<DBSource %s (%s)>' % (self.source, self.version)
2088
2089 __all__.append('DBSource')
2090
2091 @session_wrapper
2092 def source_exists(source, source_version, suites = ["any"], session=None):
2093     """
2094     Ensure that source exists somewhere in the archive for the binary
2095     upload being processed.
2096       1. exact match     => 1.0-3
2097       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
2098
2099     @type source: string
2100     @param source: source name
2101
2102     @type source_version: string
2103     @param source_version: expected source version
2104
2105     @type suites: list
2106     @param suites: list of suites to check in, default I{any}
2107
2108     @type session: Session
2109     @param session: Optional SQLA session object (a temporary one will be
2110     generated if not supplied)
2111
2112     @rtype: int
2113     @return: returns 1 if a source with expected version is found, otherwise 0
2114
2115     """
2116
2117     cnf = Config()
2118     ret = 1
2119
2120     for suite in suites:
2121         q = session.query(DBSource).filter_by(source=source)
2122         if suite != "any":
2123             # source must exist in suite X, or in some other suite that's
2124             # mapped to X, recursively... silent-maps are counted too,
2125             # unreleased-maps aren't.
2126             maps = cnf.ValueList("SuiteMappings")[:]
2127             maps.reverse()
2128             maps = [ m.split() for m in maps ]
2129             maps = [ (x[1], x[2]) for x in maps
2130                             if x[0] == "map" or x[0] == "silent-map" ]
2131             s = [suite]
2132             for x in maps:
2133                 if x[1] in s and x[0] not in s:
2134                     s.append(x[0])
2135
2136             q = q.join(SrcAssociation).join(Suite)
2137             q = q.filter(Suite.suite_name.in_(s))
2138
2139         # Reduce the query results to a list of version numbers
2140         ql = [ j.version for j in q.all() ]
2141
2142         # Try (1)
2143         if source_version in ql:
2144             continue
2145
2146         # Try (2)
2147         from daklib.regexes import re_bin_only_nmu
2148         orig_source_version = re_bin_only_nmu.sub('', source_version)
2149         if orig_source_version in ql:
2150             continue
2151
2152         # No source found so return not ok
2153         ret = 0
2154
2155     return ret
2156
2157 __all__.append('source_exists')
2158
2159 @session_wrapper
2160 def get_suites_source_in(source, session=None):
2161     """
2162     Returns list of Suite objects which given C{source} name is in
2163
2164     @type source: str
2165     @param source: DBSource package name to search for
2166
2167     @rtype: list
2168     @return: list of Suite objects for the given source
2169     """
2170
2171     return session.query(Suite).filter(Suite.sources.any(source=source)).all()
2172
2173 __all__.append('get_suites_source_in')
2174
2175 @session_wrapper
2176 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
2177     """
2178     Returns list of DBSource objects for given C{source} name and other parameters
2179
2180     @type source: str
2181     @param source: DBSource package name to search for
2182
2183     @type version: str or None
2184     @param version: DBSource version name to search for or None if not applicable
2185
2186     @type dm_upload_allowed: bool
2187     @param dm_upload_allowed: If None, no effect.  If True or False, only
2188     return packages with that dm_upload_allowed setting
2189
2190     @type session: Session
2191     @param session: Optional SQL session object (a temporary one will be
2192     generated if not supplied)
2193
2194     @rtype: list
2195     @return: list of DBSource objects for the given name (may be empty)
2196     """
2197
2198     q = session.query(DBSource).filter_by(source=source)
2199
2200     if version is not None:
2201         q = q.filter_by(version=version)
2202
2203     if dm_upload_allowed is not None:
2204         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
2205
2206     return q.all()
2207
2208 __all__.append('get_sources_from_name')
2209
2210 # FIXME: This function fails badly if it finds more than 1 source package and
2211 # its implementation is trivial enough to be inlined.
2212 @session_wrapper
2213 def get_source_in_suite(source, suite, session=None):
2214     """
2215     Returns a DBSource object for a combination of C{source} and C{suite}.
2216
2217       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2218       - B{suite} - a suite name, eg. I{unstable}
2219
2220     @type source: string
2221     @param source: source package name
2222
2223     @type suite: string
2224     @param suite: the suite name
2225
2226     @rtype: string
2227     @return: the version for I{source} in I{suite}
2228
2229     """
2230
2231     q = get_suite(suite, session).get_sources(source)
2232     try:
2233         return q.one()
2234     except NoResultFound:
2235         return None
2236
2237 __all__.append('get_source_in_suite')
2238
2239 ################################################################################
2240
2241 @session_wrapper
2242 def add_dsc_to_db(u, filename, session=None):
2243     entry = u.pkg.files[filename]
2244     source = DBSource()
2245     pfs = []
2246
2247     source.source = u.pkg.dsc["source"]
2248     source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
2249     source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
2250     source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
2251     source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2252     source.install_date = datetime.now().date()
2253
2254     dsc_component = entry["component"]
2255     dsc_location_id = entry["location id"]
2256
2257     source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
2258
2259     # Set up a new poolfile if necessary
2260     if not entry.has_key("files id") or not entry["files id"]:
2261         filename = entry["pool name"] + filename
2262         poolfile = add_poolfile(filename, entry, dsc_location_id, session)
2263         session.flush()
2264         pfs.append(poolfile)
2265         entry["files id"] = poolfile.file_id
2266
2267     source.poolfile_id = entry["files id"]
2268     session.add(source)
2269     session.flush()
2270
2271     for suite_name in u.pkg.changes["distribution"].keys():
2272         sa = SrcAssociation()
2273         sa.source_id = source.source_id
2274         sa.suite_id = get_suite(suite_name).suite_id
2275         session.add(sa)
2276
2277     session.flush()
2278
2279     # Add the source files to the DB (files and dsc_files)
2280     dscfile = DSCFile()
2281     dscfile.source_id = source.source_id
2282     dscfile.poolfile_id = entry["files id"]
2283     session.add(dscfile)
2284
2285     for dsc_file, dentry in u.pkg.dsc_files.items():
2286         df = DSCFile()
2287         df.source_id = source.source_id
2288
2289         # If the .orig tarball is already in the pool, it's
2290         # files id is stored in dsc_files by check_dsc().
2291         files_id = dentry.get("files id", None)
2292
2293         # Find the entry in the files hash
2294         # TODO: Bail out here properly
2295         dfentry = None
2296         for f, e in u.pkg.files.items():
2297             if f == dsc_file:
2298                 dfentry = e
2299                 break
2300
2301         if files_id is None:
2302             filename = dfentry["pool name"] + dsc_file
2303
2304             (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
2305             # FIXME: needs to check for -1/-2 and or handle exception
2306             if found and obj is not None:
2307                 files_id = obj.file_id
2308                 pfs.append(obj)
2309
2310             # If still not found, add it
2311             if files_id is None:
2312                 # HACK: Force sha1sum etc into dentry
2313                 dentry["sha1sum"] = dfentry["sha1sum"]
2314                 dentry["sha256sum"] = dfentry["sha256sum"]
2315                 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2316                 pfs.append(poolfile)
2317                 files_id = poolfile.file_id
2318         else:
2319             poolfile = get_poolfile_by_id(files_id, session)
2320             if poolfile is None:
2321                 utils.fubar("INTERNAL ERROR. Found no poolfile with id %d" % files_id)
2322             pfs.append(poolfile)
2323
2324         df.poolfile_id = files_id
2325         session.add(df)
2326
2327     session.flush()
2328
2329     # Add the src_uploaders to the DB
2330     uploader_ids = [source.maintainer_id]
2331     if u.pkg.dsc.has_key("uploaders"):
2332         for up in u.pkg.dsc["uploaders"].replace(">, ", ">\t").split("\t"):
2333             up = up.strip()
2334             uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2335
2336     added_ids = {}
2337     for up_id in uploader_ids:
2338         if added_ids.has_key(up_id):
2339             import utils
2340             utils.warn("Already saw uploader %s for source %s" % (up_id, source.source))
2341             continue
2342
2343         added_ids[up_id]=1
2344
2345         su = SrcUploader()
2346         su.maintainer_id = up_id
2347         su.source_id = source.source_id
2348         session.add(su)
2349
2350     session.flush()
2351
2352     return source, dsc_component, dsc_location_id, pfs
2353
2354 __all__.append('add_dsc_to_db')
2355
2356 @session_wrapper
2357 def add_deb_to_db(u, filename, session=None):
2358     """
2359     Contrary to what you might expect, this routine deals with both
2360     debs and udebs.  That info is in 'dbtype', whilst 'type' is
2361     'deb' for both of them
2362     """
2363     cnf = Config()
2364     entry = u.pkg.files[filename]
2365
2366     bin = DBBinary()
2367     bin.package = entry["package"]
2368     bin.version = entry["version"]
2369     bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2370     bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2371     bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2372     bin.binarytype = entry["dbtype"]
2373
2374     # Find poolfile id
2375     filename = entry["pool name"] + filename
2376     fullpath = os.path.join(cnf["Dir::Pool"], filename)
2377     if not entry.get("location id", None):
2378         entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], session=session).location_id
2379
2380     if entry.get("files id", None):
2381         poolfile = get_poolfile_by_id(bin.poolfile_id)
2382         bin.poolfile_id = entry["files id"]
2383     else:
2384         poolfile = add_poolfile(filename, entry, entry["location id"], session)
2385         bin.poolfile_id = entry["files id"] = poolfile.file_id
2386
2387     # Find source id
2388     bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2389     if len(bin_sources) != 1:
2390         raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2391                                   (bin.package, bin.version, entry["architecture"],
2392                                    filename, bin.binarytype, u.pkg.changes["fingerprint"])
2393
2394     bin.source_id = bin_sources[0].source_id
2395
2396     # Add and flush object so it has an ID
2397     session.add(bin)
2398     session.flush()
2399
2400     # Add BinAssociations
2401     for suite_name in u.pkg.changes["distribution"].keys():
2402         ba = BinAssociation()
2403         ba.binary_id = bin.binary_id
2404         ba.suite_id = get_suite(suite_name).suite_id
2405         session.add(ba)
2406
2407     session.flush()
2408
2409     # Deal with contents - disabled for now
2410     #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2411     #if not contents:
2412     #    print "REJECT\nCould not determine contents of package %s" % bin.package
2413     #    session.rollback()
2414     #    raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2415
2416     return poolfile
2417
2418 __all__.append('add_deb_to_db')
2419
2420 ################################################################################
2421
2422 class SourceACL(object):
2423     def __init__(self, *args, **kwargs):
2424         pass
2425
2426     def __repr__(self):
2427         return '<SourceACL %s>' % self.source_acl_id
2428
2429 __all__.append('SourceACL')
2430
2431 ################################################################################
2432
2433 class SrcAssociation(object):
2434     def __init__(self, *args, **kwargs):
2435         pass
2436
2437     def __repr__(self):
2438         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2439
2440 __all__.append('SrcAssociation')
2441
2442 ################################################################################
2443
2444 class SrcFormat(object):
2445     def __init__(self, *args, **kwargs):
2446         pass
2447
2448     def __repr__(self):
2449         return '<SrcFormat %s>' % (self.format_name)
2450
2451 __all__.append('SrcFormat')
2452
2453 ################################################################################
2454
2455 class SrcUploader(object):
2456     def __init__(self, *args, **kwargs):
2457         pass
2458
2459     def __repr__(self):
2460         return '<SrcUploader %s>' % self.uploader_id
2461
2462 __all__.append('SrcUploader')
2463
2464 ################################################################################
2465
2466 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2467                  ('SuiteID', 'suite_id'),
2468                  ('Version', 'version'),
2469                  ('Origin', 'origin'),
2470                  ('Label', 'label'),
2471                  ('Description', 'description'),
2472                  ('Untouchable', 'untouchable'),
2473                  ('Announce', 'announce'),
2474                  ('Codename', 'codename'),
2475                  ('OverrideCodename', 'overridecodename'),
2476                  ('ValidTime', 'validtime'),
2477                  ('Priority', 'priority'),
2478                  ('NotAutomatic', 'notautomatic'),
2479                  ('CopyChanges', 'copychanges'),
2480                  ('OverrideSuite', 'overridesuite')]
2481
2482 # Why the heck don't we have any UNIQUE constraints in table suite?
2483 # TODO: Add UNIQUE constraints for appropriate columns.
2484 class Suite(object):
2485     def __init__(self, suite_name = None, version = None):
2486         self.suite_name = suite_name
2487         self.version = version
2488
2489     def __repr__(self):
2490         return '<Suite %s>' % self.suite_name
2491
2492     def __eq__(self, val):
2493         if isinstance(val, str):
2494             return (self.suite_name == val)
2495         # This signals to use the normal comparison operator
2496         return NotImplemented
2497
2498     def __ne__(self, val):
2499         if isinstance(val, str):
2500             return (self.suite_name != val)
2501         # This signals to use the normal comparison operator
2502         return NotImplemented
2503
2504     def details(self):
2505         ret = []
2506         for disp, field in SUITE_FIELDS:
2507             val = getattr(self, field, None)
2508             if val is not None:
2509                 ret.append("%s: %s" % (disp, val))
2510
2511         return "\n".join(ret)
2512
2513     def get_architectures(self, skipsrc=False, skipall=False):
2514         """
2515         Returns list of Architecture objects
2516
2517         @type skipsrc: boolean
2518         @param skipsrc: Whether to skip returning the 'source' architecture entry
2519         (Default False)
2520
2521         @type skipall: boolean
2522         @param skipall: Whether to skip returning the 'all' architecture entry
2523         (Default False)
2524
2525         @rtype: list
2526         @return: list of Architecture objects for the given name (may be empty)
2527         """
2528
2529         q = object_session(self).query(Architecture). \
2530             filter(Architecture.suites.contains(self))
2531         if skipsrc:
2532             q = q.filter(Architecture.arch_string != 'source')
2533         if skipall:
2534             q = q.filter(Architecture.arch_string != 'all')
2535         return q.order_by(Architecture.arch_string).all()
2536
2537     def get_sources(self, source):
2538         """
2539         Returns a query object representing DBSource that is part of C{suite}.
2540
2541           - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2542
2543         @type source: string
2544         @param source: source package name
2545
2546         @rtype: sqlalchemy.orm.query.Query
2547         @return: a query of DBSource
2548
2549         """
2550
2551         session = object_session(self)
2552         return session.query(DBSource).filter_by(source = source). \
2553             filter(DBSource.suites.contains(self))
2554
2555 __all__.append('Suite')
2556
2557 @session_wrapper
2558 def get_suite(suite, session=None):
2559     """
2560     Returns Suite object for given C{suite name}.
2561
2562     @type suite: string
2563     @param suite: The name of the suite
2564
2565     @type session: Session
2566     @param session: Optional SQLA session object (a temporary one will be
2567     generated if not supplied)
2568
2569     @rtype: Suite
2570     @return: Suite object for the requested suite name (None if not present)
2571     """
2572
2573     q = session.query(Suite).filter_by(suite_name=suite)
2574
2575     try:
2576         return q.one()
2577     except NoResultFound:
2578         return None
2579
2580 __all__.append('get_suite')
2581
2582 ################################################################################
2583
2584 # TODO: should be removed because the implementation is too trivial
2585 @session_wrapper
2586 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2587     """
2588     Returns list of Architecture objects for given C{suite} name
2589
2590     @type suite: str
2591     @param suite: Suite name to search for
2592
2593     @type skipsrc: boolean
2594     @param skipsrc: Whether to skip returning the 'source' architecture entry
2595     (Default False)
2596
2597     @type skipall: boolean
2598     @param skipall: Whether to skip returning the 'all' architecture entry
2599     (Default False)
2600
2601     @type session: Session
2602     @param session: Optional SQL session object (a temporary one will be
2603     generated if not supplied)
2604
2605     @rtype: list
2606     @return: list of Architecture objects for the given name (may be empty)
2607     """
2608
2609     return get_suite(suite, session).get_architectures(skipsrc, skipall)
2610
2611 __all__.append('get_suite_architectures')
2612
2613 ################################################################################
2614
2615 class SuiteSrcFormat(object):
2616     def __init__(self, *args, **kwargs):
2617         pass
2618
2619     def __repr__(self):
2620         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2621
2622 __all__.append('SuiteSrcFormat')
2623
2624 @session_wrapper
2625 def get_suite_src_formats(suite, session=None):
2626     """
2627     Returns list of allowed SrcFormat for C{suite}.
2628
2629     @type suite: str
2630     @param suite: Suite name to search for
2631
2632     @type session: Session
2633     @param session: Optional SQL session object (a temporary one will be
2634     generated if not supplied)
2635
2636     @rtype: list
2637     @return: the list of allowed source formats for I{suite}
2638     """
2639
2640     q = session.query(SrcFormat)
2641     q = q.join(SuiteSrcFormat)
2642     q = q.join(Suite).filter_by(suite_name=suite)
2643     q = q.order_by('format_name')
2644
2645     return q.all()
2646
2647 __all__.append('get_suite_src_formats')
2648
2649 ################################################################################
2650
2651 class Uid(object):
2652     def __init__(self, uid = None, name = None):
2653         self.uid = uid
2654         self.name = name
2655
2656     def __eq__(self, val):
2657         if isinstance(val, str):
2658             return (self.uid == val)
2659         # This signals to use the normal comparison operator
2660         return NotImplemented
2661
2662     def __ne__(self, val):
2663         if isinstance(val, str):
2664             return (self.uid != val)
2665         # This signals to use the normal comparison operator
2666         return NotImplemented
2667
2668     def __repr__(self):
2669         return '<Uid %s (%s)>' % (self.uid, self.name)
2670
2671 __all__.append('Uid')
2672
2673 @session_wrapper
2674 def get_or_set_uid(uidname, session=None):
2675     """
2676     Returns uid object for given uidname.
2677
2678     If no matching uidname is found, a row is inserted.
2679
2680     @type uidname: string
2681     @param uidname: The uid to add
2682
2683     @type session: SQLAlchemy
2684     @param session: Optional SQL session object (a temporary one will be
2685     generated if not supplied).  If not passed, a commit will be performed at
2686     the end of the function, otherwise the caller is responsible for commiting.
2687
2688     @rtype: Uid
2689     @return: the uid object for the given uidname
2690     """
2691
2692     q = session.query(Uid).filter_by(uid=uidname)
2693
2694     try:
2695         ret = q.one()
2696     except NoResultFound:
2697         uid = Uid()
2698         uid.uid = uidname
2699         session.add(uid)
2700         session.commit_or_flush()
2701         ret = uid
2702
2703     return ret
2704
2705 __all__.append('get_or_set_uid')
2706
2707 @session_wrapper
2708 def get_uid_from_fingerprint(fpr, session=None):
2709     q = session.query(Uid)
2710     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2711
2712     try:
2713         return q.one()
2714     except NoResultFound:
2715         return None
2716
2717 __all__.append('get_uid_from_fingerprint')
2718
2719 ################################################################################
2720
2721 class UploadBlock(object):
2722     def __init__(self, *args, **kwargs):
2723         pass
2724
2725     def __repr__(self):
2726         return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2727
2728 __all__.append('UploadBlock')
2729
2730 ################################################################################
2731
2732 class DBConn(object):
2733     """
2734     database module init.
2735     """
2736     __shared_state = {}
2737
2738     def __init__(self, *args, **kwargs):
2739         self.__dict__ = self.__shared_state
2740
2741         if not getattr(self, 'initialised', False):
2742             self.initialised = True
2743             self.debug = kwargs.has_key('debug')
2744             self.__createconn()
2745
2746     def __setuptables(self):
2747         tables_with_primary = (
2748             'architecture',
2749             'archive',
2750             'bin_associations',
2751             'binaries',
2752             'binary_acl',
2753             'binary_acl_map',
2754             'build_queue',
2755             'changelogs_text',
2756             'component',
2757             'config',
2758             'changes_pending_binaries',
2759             'changes_pending_files',
2760             'changes_pending_source',
2761             'dsc_files',
2762             'files',
2763             'fingerprint',
2764             'keyrings',
2765             'keyring_acl_map',
2766             'location',
2767             'maintainer',
2768             'new_comments',
2769             'override_type',
2770             'pending_bin_contents',
2771             'policy_queue',
2772             'priority',
2773             'section',
2774             'source',
2775             'source_acl',
2776             'src_associations',
2777             'src_format',
2778             'src_uploaders',
2779             'suite',
2780             'uid',
2781             'upload_blocks',
2782             # The following tables have primary keys but sqlalchemy
2783             # version 0.5 fails to reflect them correctly with database
2784             # versions before upgrade #41.
2785             #'changes',
2786             #'build_queue_files',
2787         )
2788
2789         tables_no_primary = (
2790             'bin_contents',
2791             'changes_pending_files_map',
2792             'changes_pending_source_files',
2793             'changes_pool_files',
2794             'deb_contents',
2795             'override',
2796             'suite_architectures',
2797             'suite_src_formats',
2798             'suite_build_queue_copy',
2799             'udeb_contents',
2800             # see the comment above
2801             'changes',
2802             'build_queue_files',
2803         )
2804
2805         views = (
2806             'almost_obsolete_all_associations',
2807             'almost_obsolete_src_associations',
2808             'any_associations_source',
2809             'bin_assoc_by_arch',
2810             'bin_associations_binaries',
2811             'binaries_suite_arch',
2812             'binfiles_suite_component_arch',
2813             'changelogs',
2814             'file_arch_suite',
2815             'newest_all_associations',
2816             'newest_any_associations',
2817             'newest_source',
2818             'newest_src_association',
2819             'obsolete_all_associations',
2820             'obsolete_any_associations',
2821             'obsolete_any_by_all_associations',
2822             'obsolete_src_associations',
2823             'source_suite',
2824             'src_associations_bin',
2825             'src_associations_src',
2826             'suite_arch_by_name',
2827         )
2828
2829         # Sqlalchemy version 0.5 fails to reflect the SERIAL type
2830         # correctly and that is why we have to use a workaround. It can
2831         # be removed as soon as we switch to version 0.6.
2832         for table_name in tables_with_primary:
2833             table = Table(table_name, self.db_meta, \
2834                 Column('id', Integer, primary_key = True), \
2835                 autoload=True, useexisting=True)
2836             setattr(self, 'tbl_%s' % table_name, table)
2837
2838         for table_name in tables_no_primary:
2839             table = Table(table_name, self.db_meta, autoload=True)
2840             setattr(self, 'tbl_%s' % table_name, table)
2841
2842         for view_name in views:
2843             view = Table(view_name, self.db_meta, autoload=True)
2844             setattr(self, 'view_%s' % view_name, view)
2845
2846     def __setupmappers(self):
2847         mapper(Architecture, self.tbl_architecture,
2848            properties = dict(arch_id = self.tbl_architecture.c.id,
2849                suites = relation(Suite, secondary=self.tbl_suite_architectures,
2850                    order_by='suite_name',
2851                    backref=backref('architectures', order_by='arch_string'))))
2852
2853         mapper(Archive, self.tbl_archive,
2854                properties = dict(archive_id = self.tbl_archive.c.id,
2855                                  archive_name = self.tbl_archive.c.name))
2856
2857         mapper(BinAssociation, self.tbl_bin_associations,
2858                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2859                                  suite_id = self.tbl_bin_associations.c.suite,
2860                                  suite = relation(Suite),
2861                                  binary_id = self.tbl_bin_associations.c.bin,
2862                                  binary = relation(DBBinary)))
2863
2864         mapper(PendingBinContents, self.tbl_pending_bin_contents,
2865                properties = dict(contents_id =self.tbl_pending_bin_contents.c.id,
2866                                  filename = self.tbl_pending_bin_contents.c.filename,
2867                                  package = self.tbl_pending_bin_contents.c.package,
2868                                  version = self.tbl_pending_bin_contents.c.version,
2869                                  arch = self.tbl_pending_bin_contents.c.arch,
2870                                  otype = self.tbl_pending_bin_contents.c.type))
2871
2872         mapper(DebContents, self.tbl_deb_contents,
2873                properties = dict(binary_id=self.tbl_deb_contents.c.binary_id,
2874                                  package=self.tbl_deb_contents.c.package,
2875                                  suite=self.tbl_deb_contents.c.suite,
2876                                  arch=self.tbl_deb_contents.c.arch,
2877                                  section=self.tbl_deb_contents.c.section,
2878                                  filename=self.tbl_deb_contents.c.filename))
2879
2880         mapper(UdebContents, self.tbl_udeb_contents,
2881                properties = dict(binary_id=self.tbl_udeb_contents.c.binary_id,
2882                                  package=self.tbl_udeb_contents.c.package,
2883                                  suite=self.tbl_udeb_contents.c.suite,
2884                                  arch=self.tbl_udeb_contents.c.arch,
2885                                  section=self.tbl_udeb_contents.c.section,
2886                                  filename=self.tbl_udeb_contents.c.filename))
2887
2888         mapper(BuildQueue, self.tbl_build_queue,
2889                properties = dict(queue_id = self.tbl_build_queue.c.id))
2890
2891         mapper(BuildQueueFile, self.tbl_build_queue_files,
2892                properties = dict(buildqueue = relation(BuildQueue, backref='queuefiles'),
2893                                  poolfile = relation(PoolFile, backref='buildqueueinstances')))
2894
2895         mapper(DBBinary, self.tbl_binaries,
2896                properties = dict(binary_id = self.tbl_binaries.c.id,
2897                                  package = self.tbl_binaries.c.package,
2898                                  version = self.tbl_binaries.c.version,
2899                                  maintainer_id = self.tbl_binaries.c.maintainer,
2900                                  maintainer = relation(Maintainer),
2901                                  source_id = self.tbl_binaries.c.source,
2902                                  source = relation(DBSource),
2903                                  arch_id = self.tbl_binaries.c.architecture,
2904                                  architecture = relation(Architecture),
2905                                  poolfile_id = self.tbl_binaries.c.file,
2906                                  poolfile = relation(PoolFile),
2907                                  binarytype = self.tbl_binaries.c.type,
2908                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2909                                  fingerprint = relation(Fingerprint),
2910                                  install_date = self.tbl_binaries.c.install_date,
2911                                  binassociations = relation(BinAssociation,
2912                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2913
2914         mapper(BinaryACL, self.tbl_binary_acl,
2915                properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2916
2917         mapper(BinaryACLMap, self.tbl_binary_acl_map,
2918                properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2919                                  fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2920                                  architecture = relation(Architecture)))
2921
2922         mapper(Component, self.tbl_component,
2923                properties = dict(component_id = self.tbl_component.c.id,
2924                                  component_name = self.tbl_component.c.name))
2925
2926         mapper(DBConfig, self.tbl_config,
2927                properties = dict(config_id = self.tbl_config.c.id))
2928
2929         mapper(DSCFile, self.tbl_dsc_files,
2930                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2931                                  source_id = self.tbl_dsc_files.c.source,
2932                                  source = relation(DBSource),
2933                                  poolfile_id = self.tbl_dsc_files.c.file,
2934                                  poolfile = relation(PoolFile)))
2935
2936         mapper(PoolFile, self.tbl_files,
2937                properties = dict(file_id = self.tbl_files.c.id,
2938                                  filesize = self.tbl_files.c.size,
2939                                  location_id = self.tbl_files.c.location,
2940                                  location = relation(Location,
2941                                      # using lazy='dynamic' in the back
2942                                      # reference because we have A LOT of
2943                                      # files in one location
2944                                      backref=backref('files', lazy='dynamic'))))
2945
2946         mapper(Fingerprint, self.tbl_fingerprint,
2947                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2948                                  uid_id = self.tbl_fingerprint.c.uid,
2949                                  uid = relation(Uid),
2950                                  keyring_id = self.tbl_fingerprint.c.keyring,
2951                                  keyring = relation(Keyring),
2952                                  source_acl = relation(SourceACL),
2953                                  binary_acl = relation(BinaryACL)))
2954
2955         mapper(Keyring, self.tbl_keyrings,
2956                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2957                                  keyring_id = self.tbl_keyrings.c.id))
2958
2959         mapper(DBChange, self.tbl_changes,
2960                properties = dict(change_id = self.tbl_changes.c.id,
2961                                  poolfiles = relation(PoolFile,
2962                                                       secondary=self.tbl_changes_pool_files,
2963                                                       backref="changeslinks"),
2964                                  seen = self.tbl_changes.c.seen,
2965                                  source = self.tbl_changes.c.source,
2966                                  binaries = self.tbl_changes.c.binaries,
2967                                  architecture = self.tbl_changes.c.architecture,
2968                                  distribution = self.tbl_changes.c.distribution,
2969                                  urgency = self.tbl_changes.c.urgency,
2970                                  maintainer = self.tbl_changes.c.maintainer,
2971                                  changedby = self.tbl_changes.c.changedby,
2972                                  date = self.tbl_changes.c.date,
2973                                  version = self.tbl_changes.c.version,
2974                                  files = relation(ChangePendingFile,
2975                                                   secondary=self.tbl_changes_pending_files_map,
2976                                                   backref="changesfile"),
2977                                  in_queue_id = self.tbl_changes.c.in_queue,
2978                                  in_queue = relation(PolicyQueue,
2979                                                      primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
2980                                  approved_for_id = self.tbl_changes.c.approved_for))
2981
2982         mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
2983                properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
2984
2985         mapper(ChangePendingFile, self.tbl_changes_pending_files,
2986                properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id,
2987                                  filename = self.tbl_changes_pending_files.c.filename,
2988                                  size = self.tbl_changes_pending_files.c.size,
2989                                  md5sum = self.tbl_changes_pending_files.c.md5sum,
2990                                  sha1sum = self.tbl_changes_pending_files.c.sha1sum,
2991                                  sha256sum = self.tbl_changes_pending_files.c.sha256sum))
2992
2993         mapper(ChangePendingSource, self.tbl_changes_pending_source,
2994                properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
2995                                  change = relation(DBChange),
2996                                  maintainer = relation(Maintainer,
2997                                                        primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
2998                                  changedby = relation(Maintainer,
2999                                                       primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
3000                                  fingerprint = relation(Fingerprint),
3001                                  source_files = relation(ChangePendingFile,
3002                                                          secondary=self.tbl_changes_pending_source_files,
3003                                                          backref="pending_sources")))
3004
3005
3006         mapper(KeyringACLMap, self.tbl_keyring_acl_map,
3007                properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
3008                                  keyring = relation(Keyring, backref="keyring_acl_map"),
3009                                  architecture = relation(Architecture)))
3010
3011         mapper(Location, self.tbl_location,
3012                properties = dict(location_id = self.tbl_location.c.id,
3013                                  component_id = self.tbl_location.c.component,
3014                                  component = relation(Component),
3015                                  archive_id = self.tbl_location.c.archive,
3016                                  archive = relation(Archive),
3017                                  # FIXME: the 'type' column is old cruft and
3018                                  # should be removed in the future.
3019                                  archive_type = self.tbl_location.c.type))
3020
3021         mapper(Maintainer, self.tbl_maintainer,
3022                properties = dict(maintainer_id = self.tbl_maintainer.c.id,
3023                    maintains_sources = relation(DBSource, backref='maintainer',
3024                        primaryjoin=(self.tbl_maintainer.c.id==self.tbl_source.c.maintainer)),
3025                    changed_sources = relation(DBSource, backref='changedby',
3026                        primaryjoin=(self.tbl_maintainer.c.id==self.tbl_source.c.changedby))))
3027
3028         mapper(NewComment, self.tbl_new_comments,
3029                properties = dict(comment_id = self.tbl_new_comments.c.id))
3030
3031         mapper(Override, self.tbl_override,
3032                properties = dict(suite_id = self.tbl_override.c.suite,
3033                                  suite = relation(Suite),
3034                                  package = self.tbl_override.c.package,
3035                                  component_id = self.tbl_override.c.component,
3036                                  component = relation(Component),
3037                                  priority_id = self.tbl_override.c.priority,
3038                                  priority = relation(Priority),
3039                                  section_id = self.tbl_override.c.section,
3040                                  section = relation(Section),
3041                                  overridetype_id = self.tbl_override.c.type,
3042                                  overridetype = relation(OverrideType)))
3043
3044         mapper(OverrideType, self.tbl_override_type,
3045                properties = dict(overridetype = self.tbl_override_type.c.type,
3046                                  overridetype_id = self.tbl_override_type.c.id))
3047
3048         mapper(PolicyQueue, self.tbl_policy_queue,
3049                properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
3050
3051         mapper(Priority, self.tbl_priority,
3052                properties = dict(priority_id = self.tbl_priority.c.id))
3053
3054         mapper(Section, self.tbl_section,
3055                properties = dict(section_id = self.tbl_section.c.id,
3056                                  section=self.tbl_section.c.section))
3057
3058         mapper(DBSource, self.tbl_source,
3059                properties = dict(source_id = self.tbl_source.c.id,
3060                                  version = self.tbl_source.c.version,
3061                                  maintainer_id = self.tbl_source.c.maintainer,
3062                                  poolfile_id = self.tbl_source.c.file,
3063                                  poolfile = relation(PoolFile, backref=backref('source', uselist = False)),
3064                                  fingerprint_id = self.tbl_source.c.sig_fpr,
3065                                  fingerprint = relation(Fingerprint),
3066                                  changedby_id = self.tbl_source.c.changedby,
3067                                  srcfiles = relation(DSCFile,
3068                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
3069                                  suites = relation(Suite, secondary=self.tbl_src_associations,
3070                                      backref='sources'),
3071                                  srcuploaders = relation(SrcUploader)))
3072
3073         mapper(SourceACL, self.tbl_source_acl,
3074                properties = dict(source_acl_id = self.tbl_source_acl.c.id))
3075
3076         mapper(SrcAssociation, self.tbl_src_associations,
3077                properties = dict(sa_id = self.tbl_src_associations.c.id,
3078                                  suite_id = self.tbl_src_associations.c.suite,
3079                                  suite = relation(Suite),
3080                                  source_id = self.tbl_src_associations.c.source,
3081                                  source = relation(DBSource)))
3082
3083         mapper(SrcFormat, self.tbl_src_format,
3084                properties = dict(src_format_id = self.tbl_src_format.c.id,
3085                                  format_name = self.tbl_src_format.c.format_name))
3086
3087         mapper(SrcUploader, self.tbl_src_uploaders,
3088                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
3089                                  source_id = self.tbl_src_uploaders.c.source,
3090                                  source = relation(DBSource,
3091                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
3092                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
3093                                  maintainer = relation(Maintainer,
3094                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
3095
3096         mapper(Suite, self.tbl_suite,
3097                properties = dict(suite_id = self.tbl_suite.c.id,
3098                                  policy_queue = relation(PolicyQueue),
3099                                  copy_queues = relation(BuildQueue, secondary=self.tbl_suite_build_queue_copy)))
3100
3101         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3102                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3103                                  suite = relation(Suite, backref='suitesrcformats'),
3104                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
3105                                  src_format = relation(SrcFormat)))
3106
3107         mapper(Uid, self.tbl_uid,
3108                properties = dict(uid_id = self.tbl_uid.c.id,
3109                                  fingerprint = relation(Fingerprint)))
3110
3111         mapper(UploadBlock, self.tbl_upload_blocks,
3112                properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3113                                  fingerprint = relation(Fingerprint, backref="uploadblocks"),
3114                                  uid = relation(Uid, backref="uploadblocks")))
3115
3116     ## Connection functions
3117     def __createconn(self):
3118         from config import Config
3119         cnf = Config()
3120         if cnf["DB::Host"]:
3121             # TCP/IP
3122             connstr = "postgres://%s" % cnf["DB::Host"]
3123             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3124                 connstr += ":%s" % cnf["DB::Port"]
3125             connstr += "/%s" % cnf["DB::Name"]
3126         else:
3127             # Unix Socket
3128             connstr = "postgres:///%s" % cnf["DB::Name"]
3129             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3130                 connstr += "?port=%s" % cnf["DB::Port"]
3131
3132         self.db_pg   = create_engine(connstr, echo=self.debug)
3133         self.db_meta = MetaData()
3134         self.db_meta.bind = self.db_pg
3135         self.db_smaker = sessionmaker(bind=self.db_pg,
3136                                       autoflush=True,
3137                                       autocommit=False)
3138
3139         self.__setuptables()
3140         self.__setupmappers()
3141
3142     def session(self):
3143         return self.db_smaker()
3144
3145 __all__.append('DBConn')
3146
3147