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