]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
Merge branch 'dbtests' into merge
[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', 'location_id', 'archive_type', 'component', \
1587             'files_count']
1588
1589     def not_null_constraints(self):
1590         return ['path', 'archive_type']
1591
1592 __all__.append('Location')
1593
1594 @session_wrapper
1595 def get_location(location, component=None, archive=None, session=None):
1596     """
1597     Returns Location object for the given combination of location, component
1598     and archive
1599
1600     @type location: string
1601     @param location: the path of the location, e.g. I{/srv/ftp-master.debian.org/ftp/pool/}
1602
1603     @type component: string
1604     @param component: the component name (if None, no restriction applied)
1605
1606     @type archive: string
1607     @param archive: the archive name (if None, no restriction applied)
1608
1609     @rtype: Location / None
1610     @return: Either a Location object or None if one can't be found
1611     """
1612
1613     q = session.query(Location).filter_by(path=location)
1614
1615     if archive is not None:
1616         q = q.join(Archive).filter_by(archive_name=archive)
1617
1618     if component is not None:
1619         q = q.join(Component).filter_by(component_name=component)
1620
1621     try:
1622         return q.one()
1623     except NoResultFound:
1624         return None
1625
1626 __all__.append('get_location')
1627
1628 ################################################################################
1629
1630 class Maintainer(ORMObject):
1631     def __init__(self, name = None):
1632         self.name = name
1633
1634     def properties(self):
1635         return ['name', 'maintainer_id']
1636
1637     def not_null_constraints(self):
1638         return ['name']
1639
1640     def get_split_maintainer(self):
1641         if not hasattr(self, 'name') or self.name is None:
1642             return ('', '', '', '')
1643
1644         return fix_maintainer(self.name.strip())
1645
1646 __all__.append('Maintainer')
1647
1648 @session_wrapper
1649 def get_or_set_maintainer(name, session=None):
1650     """
1651     Returns Maintainer object for given maintainer name.
1652
1653     If no matching maintainer name is found, a row is inserted.
1654
1655     @type name: string
1656     @param name: The maintainer name to add
1657
1658     @type session: SQLAlchemy
1659     @param session: Optional SQL session object (a temporary one will be
1660     generated if not supplied).  If not passed, a commit will be performed at
1661     the end of the function, otherwise the caller is responsible for commiting.
1662     A flush will be performed either way.
1663
1664     @rtype: Maintainer
1665     @return: the Maintainer object for the given maintainer
1666     """
1667
1668     q = session.query(Maintainer).filter_by(name=name)
1669     try:
1670         ret = q.one()
1671     except NoResultFound:
1672         maintainer = Maintainer()
1673         maintainer.name = name
1674         session.add(maintainer)
1675         session.commit_or_flush()
1676         ret = maintainer
1677
1678     return ret
1679
1680 __all__.append('get_or_set_maintainer')
1681
1682 @session_wrapper
1683 def get_maintainer(maintainer_id, session=None):
1684     """
1685     Return the name of the maintainer behind C{maintainer_id} or None if that
1686     maintainer_id is invalid.
1687
1688     @type maintainer_id: int
1689     @param maintainer_id: the id of the maintainer
1690
1691     @rtype: Maintainer
1692     @return: the Maintainer with this C{maintainer_id}
1693     """
1694
1695     return session.query(Maintainer).get(maintainer_id)
1696
1697 __all__.append('get_maintainer')
1698
1699 ################################################################################
1700
1701 class NewComment(object):
1702     def __init__(self, *args, **kwargs):
1703         pass
1704
1705     def __repr__(self):
1706         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1707
1708 __all__.append('NewComment')
1709
1710 @session_wrapper
1711 def has_new_comment(package, version, session=None):
1712     """
1713     Returns true if the given combination of C{package}, C{version} has a comment.
1714
1715     @type package: string
1716     @param package: name of the package
1717
1718     @type version: string
1719     @param version: package version
1720
1721     @type session: Session
1722     @param session: Optional SQLA session object (a temporary one will be
1723     generated if not supplied)
1724
1725     @rtype: boolean
1726     @return: true/false
1727     """
1728
1729     q = session.query(NewComment)
1730     q = q.filter_by(package=package)
1731     q = q.filter_by(version=version)
1732
1733     return bool(q.count() > 0)
1734
1735 __all__.append('has_new_comment')
1736
1737 @session_wrapper
1738 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1739     """
1740     Returns (possibly empty) list of NewComment objects for the given
1741     parameters
1742
1743     @type package: string (optional)
1744     @param package: name of the package
1745
1746     @type version: string (optional)
1747     @param version: package version
1748
1749     @type comment_id: int (optional)
1750     @param comment_id: An id of a comment
1751
1752     @type session: Session
1753     @param session: Optional SQLA session object (a temporary one will be
1754     generated if not supplied)
1755
1756     @rtype: list
1757     @return: A (possibly empty) list of NewComment objects will be returned
1758     """
1759
1760     q = session.query(NewComment)
1761     if package is not None: q = q.filter_by(package=package)
1762     if version is not None: q = q.filter_by(version=version)
1763     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1764
1765     return q.all()
1766
1767 __all__.append('get_new_comments')
1768
1769 ################################################################################
1770
1771 class Override(object):
1772     def __init__(self, *args, **kwargs):
1773         pass
1774
1775     def __repr__(self):
1776         return '<Override %s (%s)>' % (self.package, self.suite_id)
1777
1778 __all__.append('Override')
1779
1780 @session_wrapper
1781 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1782     """
1783     Returns Override object for the given parameters
1784
1785     @type package: string
1786     @param package: The name of the package
1787
1788     @type suite: string, list or None
1789     @param suite: The name of the suite (or suites if a list) to limit to.  If
1790                   None, don't limit.  Defaults to None.
1791
1792     @type component: string, list or None
1793     @param component: The name of the component (or components if a list) to
1794                       limit to.  If None, don't limit.  Defaults to None.
1795
1796     @type overridetype: string, list or None
1797     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1798                          limit to.  If None, don't limit.  Defaults to None.
1799
1800     @type session: Session
1801     @param session: Optional SQLA session object (a temporary one will be
1802     generated if not supplied)
1803
1804     @rtype: list
1805     @return: A (possibly empty) list of Override objects will be returned
1806     """
1807
1808     q = session.query(Override)
1809     q = q.filter_by(package=package)
1810
1811     if suite is not None:
1812         if not isinstance(suite, list): suite = [suite]
1813         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1814
1815     if component is not None:
1816         if not isinstance(component, list): component = [component]
1817         q = q.join(Component).filter(Component.component_name.in_(component))
1818
1819     if overridetype is not None:
1820         if not isinstance(overridetype, list): overridetype = [overridetype]
1821         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1822
1823     return q.all()
1824
1825 __all__.append('get_override')
1826
1827
1828 ################################################################################
1829
1830 class OverrideType(object):
1831     def __init__(self, *args, **kwargs):
1832         pass
1833
1834     def __repr__(self):
1835         return '<OverrideType %s>' % self.overridetype
1836
1837 __all__.append('OverrideType')
1838
1839 @session_wrapper
1840 def get_override_type(override_type, session=None):
1841     """
1842     Returns OverrideType object for given C{override type}.
1843
1844     @type override_type: string
1845     @param override_type: The name of the override type
1846
1847     @type session: Session
1848     @param session: Optional SQLA session object (a temporary one will be
1849     generated if not supplied)
1850
1851     @rtype: int
1852     @return: the database id for the given override type
1853     """
1854
1855     q = session.query(OverrideType).filter_by(overridetype=override_type)
1856
1857     try:
1858         return q.one()
1859     except NoResultFound:
1860         return None
1861
1862 __all__.append('get_override_type')
1863
1864 ################################################################################
1865
1866 class DebContents(object):
1867     def __init__(self, *args, **kwargs):
1868         pass
1869
1870     def __repr__(self):
1871         return '<DebConetnts %s: %s>' % (self.package.package,self.file)
1872
1873 __all__.append('DebContents')
1874
1875
1876 class UdebContents(object):
1877     def __init__(self, *args, **kwargs):
1878         pass
1879
1880     def __repr__(self):
1881         return '<UdebConetnts %s: %s>' % (self.package.package,self.file)
1882
1883 __all__.append('UdebContents')
1884
1885 class PendingBinContents(object):
1886     def __init__(self, *args, **kwargs):
1887         pass
1888
1889     def __repr__(self):
1890         return '<PendingBinContents %s>' % self.contents_id
1891
1892 __all__.append('PendingBinContents')
1893
1894 def insert_pending_content_paths(package,
1895                                  is_udeb,
1896                                  fullpaths,
1897                                  session=None):
1898     """
1899     Make sure given paths are temporarily associated with given
1900     package
1901
1902     @type package: dict
1903     @param package: the package to associate with should have been read in from the binary control file
1904     @type fullpaths: list
1905     @param fullpaths: the list of paths of the file being associated with the binary
1906     @type session: SQLAlchemy session
1907     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1908     is responsible for ensuring a transaction has begun and committing the
1909     results or rolling back based on the result code.  If not passed, a commit
1910     will be performed at the end of the function
1911
1912     @return: True upon success, False if there is a problem
1913     """
1914
1915     privatetrans = False
1916
1917     if session is None:
1918         session = DBConn().session()
1919         privatetrans = True
1920
1921     try:
1922         arch = get_architecture(package['Architecture'], session)
1923         arch_id = arch.arch_id
1924
1925         # Remove any already existing recorded files for this package
1926         q = session.query(PendingBinContents)
1927         q = q.filter_by(package=package['Package'])
1928         q = q.filter_by(version=package['Version'])
1929         q = q.filter_by(architecture=arch_id)
1930         q.delete()
1931
1932         for fullpath in fullpaths:
1933
1934             if fullpath.startswith( "./" ):
1935                 fullpath = fullpath[2:]
1936
1937             pca = PendingBinContents()
1938             pca.package = package['Package']
1939             pca.version = package['Version']
1940             pca.file = fullpath
1941             pca.architecture = arch_id
1942
1943             if isudeb:
1944                 pca.type = 8 # gross
1945             else:
1946                 pca.type = 7 # also gross
1947             session.add(pca)
1948
1949         # Only commit if we set up the session ourself
1950         if privatetrans:
1951             session.commit()
1952             session.close()
1953         else:
1954             session.flush()
1955
1956         return True
1957     except Exception, e:
1958         traceback.print_exc()
1959
1960         # Only rollback if we set up the session ourself
1961         if privatetrans:
1962             session.rollback()
1963             session.close()
1964
1965         return False
1966
1967 __all__.append('insert_pending_content_paths')
1968
1969 ################################################################################
1970
1971 class PolicyQueue(object):
1972     def __init__(self, *args, **kwargs):
1973         pass
1974
1975     def __repr__(self):
1976         return '<PolicyQueue %s>' % self.queue_name
1977
1978 __all__.append('PolicyQueue')
1979
1980 @session_wrapper
1981 def get_policy_queue(queuename, session=None):
1982     """
1983     Returns PolicyQueue object for given C{queue name}
1984
1985     @type queuename: string
1986     @param queuename: The name of the queue
1987
1988     @type session: Session
1989     @param session: Optional SQLA session object (a temporary one will be
1990     generated if not supplied)
1991
1992     @rtype: PolicyQueue
1993     @return: PolicyQueue object for the given queue
1994     """
1995
1996     q = session.query(PolicyQueue).filter_by(queue_name=queuename)
1997
1998     try:
1999         return q.one()
2000     except NoResultFound:
2001         return None
2002
2003 __all__.append('get_policy_queue')
2004
2005 @session_wrapper
2006 def get_policy_queue_from_path(pathname, session=None):
2007     """
2008     Returns PolicyQueue object for given C{path name}
2009
2010     @type queuename: string
2011     @param queuename: The path
2012
2013     @type session: Session
2014     @param session: Optional SQLA session object (a temporary one will be
2015     generated if not supplied)
2016
2017     @rtype: PolicyQueue
2018     @return: PolicyQueue object for the given queue
2019     """
2020
2021     q = session.query(PolicyQueue).filter_by(path=pathname)
2022
2023     try:
2024         return q.one()
2025     except NoResultFound:
2026         return None
2027
2028 __all__.append('get_policy_queue_from_path')
2029
2030 ################################################################################
2031
2032 class Priority(object):
2033     def __init__(self, *args, **kwargs):
2034         pass
2035
2036     def __eq__(self, val):
2037         if isinstance(val, str):
2038             return (self.priority == val)
2039         # This signals to use the normal comparison operator
2040         return NotImplemented
2041
2042     def __ne__(self, val):
2043         if isinstance(val, str):
2044             return (self.priority != val)
2045         # This signals to use the normal comparison operator
2046         return NotImplemented
2047
2048     def __repr__(self):
2049         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
2050
2051 __all__.append('Priority')
2052
2053 @session_wrapper
2054 def get_priority(priority, session=None):
2055     """
2056     Returns Priority object for given C{priority name}.
2057
2058     @type priority: string
2059     @param priority: The name of the priority
2060
2061     @type session: Session
2062     @param session: Optional SQLA session object (a temporary one will be
2063     generated if not supplied)
2064
2065     @rtype: Priority
2066     @return: Priority object for the given priority
2067     """
2068
2069     q = session.query(Priority).filter_by(priority=priority)
2070
2071     try:
2072         return q.one()
2073     except NoResultFound:
2074         return None
2075
2076 __all__.append('get_priority')
2077
2078 @session_wrapper
2079 def get_priorities(session=None):
2080     """
2081     Returns dictionary of priority names -> id mappings
2082
2083     @type session: Session
2084     @param session: Optional SQL session object (a temporary one will be
2085     generated if not supplied)
2086
2087     @rtype: dictionary
2088     @return: dictionary of priority names -> id mappings
2089     """
2090
2091     ret = {}
2092     q = session.query(Priority)
2093     for x in q.all():
2094         ret[x.priority] = x.priority_id
2095
2096     return ret
2097
2098 __all__.append('get_priorities')
2099
2100 ################################################################################
2101
2102 class Section(object):
2103     def __init__(self, *args, **kwargs):
2104         pass
2105
2106     def __eq__(self, val):
2107         if isinstance(val, str):
2108             return (self.section == val)
2109         # This signals to use the normal comparison operator
2110         return NotImplemented
2111
2112     def __ne__(self, val):
2113         if isinstance(val, str):
2114             return (self.section != val)
2115         # This signals to use the normal comparison operator
2116         return NotImplemented
2117
2118     def __repr__(self):
2119         return '<Section %s>' % self.section
2120
2121 __all__.append('Section')
2122
2123 @session_wrapper
2124 def get_section(section, session=None):
2125     """
2126     Returns Section object for given C{section name}.
2127
2128     @type section: string
2129     @param section: The name of the section
2130
2131     @type session: Session
2132     @param session: Optional SQLA session object (a temporary one will be
2133     generated if not supplied)
2134
2135     @rtype: Section
2136     @return: Section object for the given section name
2137     """
2138
2139     q = session.query(Section).filter_by(section=section)
2140
2141     try:
2142         return q.one()
2143     except NoResultFound:
2144         return None
2145
2146 __all__.append('get_section')
2147
2148 @session_wrapper
2149 def get_sections(session=None):
2150     """
2151     Returns dictionary of section names -> id mappings
2152
2153     @type session: Session
2154     @param session: Optional SQL session object (a temporary one will be
2155     generated if not supplied)
2156
2157     @rtype: dictionary
2158     @return: dictionary of section names -> id mappings
2159     """
2160
2161     ret = {}
2162     q = session.query(Section)
2163     for x in q.all():
2164         ret[x.section] = x.section_id
2165
2166     return ret
2167
2168 __all__.append('get_sections')
2169
2170 ################################################################################
2171
2172 class DBSource(ORMObject):
2173     def __init__(self, source = None, version = None, maintainer = None, \
2174         changedby = None, poolfile = None, install_date = None):
2175         self.source = source
2176         self.version = version
2177         self.maintainer = maintainer
2178         self.changedby = changedby
2179         self.poolfile = poolfile
2180         self.install_date = install_date
2181
2182     def properties(self):
2183         return ['source', 'source_id', 'maintainer', 'changedby', \
2184             'fingerprint', 'poolfile', 'version', 'suites_count', \
2185             'install_date', 'binaries_count']
2186
2187     def not_null_constraints(self):
2188         return ['source', 'version', 'install_date', 'maintainer', \
2189             'changedby', 'poolfile', 'install_date']
2190
2191 __all__.append('DBSource')
2192
2193 @session_wrapper
2194 def source_exists(source, source_version, suites = ["any"], session=None):
2195     """
2196     Ensure that source exists somewhere in the archive for the binary
2197     upload being processed.
2198       1. exact match     => 1.0-3
2199       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
2200
2201     @type source: string
2202     @param source: source name
2203
2204     @type source_version: string
2205     @param source_version: expected source version
2206
2207     @type suites: list
2208     @param suites: list of suites to check in, default I{any}
2209
2210     @type session: Session
2211     @param session: Optional SQLA session object (a temporary one will be
2212     generated if not supplied)
2213
2214     @rtype: int
2215     @return: returns 1 if a source with expected version is found, otherwise 0
2216
2217     """
2218
2219     cnf = Config()
2220     ret = True
2221
2222     from daklib.regexes import re_bin_only_nmu
2223     orig_source_version = re_bin_only_nmu.sub('', source_version)
2224
2225     for suite in suites:
2226         q = session.query(DBSource).filter_by(source=source). \
2227             filter(DBSource.version.in_([source_version, orig_source_version]))
2228         if suite != "any":
2229             # source must exist in suite X, or in some other suite that's
2230             # mapped to X, recursively... silent-maps are counted too,
2231             # unreleased-maps aren't.
2232             maps = cnf.ValueList("SuiteMappings")[:]
2233             maps.reverse()
2234             maps = [ m.split() for m in maps ]
2235             maps = [ (x[1], x[2]) for x in maps
2236                             if x[0] == "map" or x[0] == "silent-map" ]
2237             s = [suite]
2238             for x in maps:
2239                 if x[1] in s and x[0] not in s:
2240                     s.append(x[0])
2241
2242             q = q.filter(DBSource.suites.any(Suite.suite_name.in_(s)))
2243
2244         if q.count() > 0:
2245             continue
2246
2247         # No source found so return not ok
2248         ret = False
2249
2250     return ret
2251
2252 __all__.append('source_exists')
2253
2254 @session_wrapper
2255 def get_suites_source_in(source, session=None):
2256     """
2257     Returns list of Suite objects which given C{source} name is in
2258
2259     @type source: str
2260     @param source: DBSource package name to search for
2261
2262     @rtype: list
2263     @return: list of Suite objects for the given source
2264     """
2265
2266     return session.query(Suite).filter(Suite.sources.any(source=source)).all()
2267
2268 __all__.append('get_suites_source_in')
2269
2270 @session_wrapper
2271 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
2272     """
2273     Returns list of DBSource objects for given C{source} name and other parameters
2274
2275     @type source: str
2276     @param source: DBSource package name to search for
2277
2278     @type version: str or None
2279     @param version: DBSource version name to search for or None if not applicable
2280
2281     @type dm_upload_allowed: bool
2282     @param dm_upload_allowed: If None, no effect.  If True or False, only
2283     return packages with that dm_upload_allowed setting
2284
2285     @type session: Session
2286     @param session: Optional SQL session object (a temporary one will be
2287     generated if not supplied)
2288
2289     @rtype: list
2290     @return: list of DBSource objects for the given name (may be empty)
2291     """
2292
2293     q = session.query(DBSource).filter_by(source=source)
2294
2295     if version is not None:
2296         q = q.filter_by(version=version)
2297
2298     if dm_upload_allowed is not None:
2299         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
2300
2301     return q.all()
2302
2303 __all__.append('get_sources_from_name')
2304
2305 # FIXME: This function fails badly if it finds more than 1 source package and
2306 # its implementation is trivial enough to be inlined.
2307 @session_wrapper
2308 def get_source_in_suite(source, suite, session=None):
2309     """
2310     Returns a DBSource object for a combination of C{source} and C{suite}.
2311
2312       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2313       - B{suite} - a suite name, eg. I{unstable}
2314
2315     @type source: string
2316     @param source: source package name
2317
2318     @type suite: string
2319     @param suite: the suite name
2320
2321     @rtype: string
2322     @return: the version for I{source} in I{suite}
2323
2324     """
2325
2326     q = get_suite(suite, session).get_sources(source)
2327     try:
2328         return q.one()
2329     except NoResultFound:
2330         return None
2331
2332 __all__.append('get_source_in_suite')
2333
2334 ################################################################################
2335
2336 @session_wrapper
2337 def add_dsc_to_db(u, filename, session=None):
2338     entry = u.pkg.files[filename]
2339     source = DBSource()
2340     pfs = []
2341
2342     source.source = u.pkg.dsc["source"]
2343     source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
2344     source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
2345     source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
2346     source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2347     source.install_date = datetime.now().date()
2348
2349     dsc_component = entry["component"]
2350     dsc_location_id = entry["location id"]
2351
2352     source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
2353
2354     # Set up a new poolfile if necessary
2355     if not entry.has_key("files id") or not entry["files id"]:
2356         filename = entry["pool name"] + filename
2357         poolfile = add_poolfile(filename, entry, dsc_location_id, session)
2358         session.flush()
2359         pfs.append(poolfile)
2360         entry["files id"] = poolfile.file_id
2361
2362     source.poolfile_id = entry["files id"]
2363     session.add(source)
2364
2365     suite_names = u.pkg.changes["distribution"].keys()
2366     source.suites = session.query(Suite). \
2367         filter(Suite.suite_name.in_(suite_names)).all()
2368
2369     # Add the source files to the DB (files and dsc_files)
2370     dscfile = DSCFile()
2371     dscfile.source_id = source.source_id
2372     dscfile.poolfile_id = entry["files id"]
2373     session.add(dscfile)
2374
2375     for dsc_file, dentry in u.pkg.dsc_files.items():
2376         df = DSCFile()
2377         df.source_id = source.source_id
2378
2379         # If the .orig tarball is already in the pool, it's
2380         # files id is stored in dsc_files by check_dsc().
2381         files_id = dentry.get("files id", None)
2382
2383         # Find the entry in the files hash
2384         # TODO: Bail out here properly
2385         dfentry = None
2386         for f, e in u.pkg.files.items():
2387             if f == dsc_file:
2388                 dfentry = e
2389                 break
2390
2391         if files_id is None:
2392             filename = dfentry["pool name"] + dsc_file
2393
2394             (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
2395             # FIXME: needs to check for -1/-2 and or handle exception
2396             if found and obj is not None:
2397                 files_id = obj.file_id
2398                 pfs.append(obj)
2399
2400             # If still not found, add it
2401             if files_id is None:
2402                 # HACK: Force sha1sum etc into dentry
2403                 dentry["sha1sum"] = dfentry["sha1sum"]
2404                 dentry["sha256sum"] = dfentry["sha256sum"]
2405                 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2406                 pfs.append(poolfile)
2407                 files_id = poolfile.file_id
2408         else:
2409             poolfile = get_poolfile_by_id(files_id, session)
2410             if poolfile is None:
2411                 utils.fubar("INTERNAL ERROR. Found no poolfile with id %d" % files_id)
2412             pfs.append(poolfile)
2413
2414         df.poolfile_id = files_id
2415         session.add(df)
2416
2417     # Add the src_uploaders to the DB
2418     uploader_ids = [source.maintainer_id]
2419     if u.pkg.dsc.has_key("uploaders"):
2420         for up in u.pkg.dsc["uploaders"].replace(">, ", ">\t").split("\t"):
2421             up = up.strip()
2422             uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2423
2424     added_ids = {}
2425     for up_id in uploader_ids:
2426         if added_ids.has_key(up_id):
2427             import utils
2428             utils.warn("Already saw uploader %s for source %s" % (up_id, source.source))
2429             continue
2430
2431         added_ids[up_id]=1
2432
2433         su = SrcUploader()
2434         su.maintainer_id = up_id
2435         su.source_id = source.source_id
2436         session.add(su)
2437
2438     session.flush()
2439
2440     return source, dsc_component, dsc_location_id, pfs
2441
2442 __all__.append('add_dsc_to_db')
2443
2444 @session_wrapper
2445 def add_deb_to_db(u, filename, session=None):
2446     """
2447     Contrary to what you might expect, this routine deals with both
2448     debs and udebs.  That info is in 'dbtype', whilst 'type' is
2449     'deb' for both of them
2450     """
2451     cnf = Config()
2452     entry = u.pkg.files[filename]
2453
2454     bin = DBBinary()
2455     bin.package = entry["package"]
2456     bin.version = entry["version"]
2457     bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2458     bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2459     bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2460     bin.binarytype = entry["dbtype"]
2461
2462     # Find poolfile id
2463     filename = entry["pool name"] + filename
2464     fullpath = os.path.join(cnf["Dir::Pool"], filename)
2465     if not entry.get("location id", None):
2466         entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], session=session).location_id
2467
2468     if entry.get("files id", None):
2469         poolfile = get_poolfile_by_id(bin.poolfile_id)
2470         bin.poolfile_id = entry["files id"]
2471     else:
2472         poolfile = add_poolfile(filename, entry, entry["location id"], session)
2473         bin.poolfile_id = entry["files id"] = poolfile.file_id
2474
2475     # Find source id
2476     bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2477     if len(bin_sources) != 1:
2478         raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2479                                   (bin.package, bin.version, entry["architecture"],
2480                                    filename, bin.binarytype, u.pkg.changes["fingerprint"])
2481
2482     bin.source_id = bin_sources[0].source_id
2483
2484     # Add and flush object so it has an ID
2485     session.add(bin)
2486
2487     suite_names = u.pkg.changes["distribution"].keys()
2488     bin.suites = session.query(Suite). \
2489         filter(Suite.suite_name.in_(suite_names)).all()
2490
2491     session.flush()
2492
2493     # Deal with contents - disabled for now
2494     #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2495     #if not contents:
2496     #    print "REJECT\nCould not determine contents of package %s" % bin.package
2497     #    session.rollback()
2498     #    raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2499
2500     return poolfile
2501
2502 __all__.append('add_deb_to_db')
2503
2504 ################################################################################
2505
2506 class SourceACL(object):
2507     def __init__(self, *args, **kwargs):
2508         pass
2509
2510     def __repr__(self):
2511         return '<SourceACL %s>' % self.source_acl_id
2512
2513 __all__.append('SourceACL')
2514
2515 ################################################################################
2516
2517 class SrcFormat(object):
2518     def __init__(self, *args, **kwargs):
2519         pass
2520
2521     def __repr__(self):
2522         return '<SrcFormat %s>' % (self.format_name)
2523
2524 __all__.append('SrcFormat')
2525
2526 ################################################################################
2527
2528 class SrcUploader(object):
2529     def __init__(self, *args, **kwargs):
2530         pass
2531
2532     def __repr__(self):
2533         return '<SrcUploader %s>' % self.uploader_id
2534
2535 __all__.append('SrcUploader')
2536
2537 ################################################################################
2538
2539 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2540                  ('SuiteID', 'suite_id'),
2541                  ('Version', 'version'),
2542                  ('Origin', 'origin'),
2543                  ('Label', 'label'),
2544                  ('Description', 'description'),
2545                  ('Untouchable', 'untouchable'),
2546                  ('Announce', 'announce'),
2547                  ('Codename', 'codename'),
2548                  ('OverrideCodename', 'overridecodename'),
2549                  ('ValidTime', 'validtime'),
2550                  ('Priority', 'priority'),
2551                  ('NotAutomatic', 'notautomatic'),
2552                  ('CopyChanges', 'copychanges'),
2553                  ('OverrideSuite', 'overridesuite')]
2554
2555 # Why the heck don't we have any UNIQUE constraints in table suite?
2556 # TODO: Add UNIQUE constraints for appropriate columns.
2557 class Suite(ORMObject):
2558     def __init__(self, suite_name = None, version = None):
2559         self.suite_name = suite_name
2560         self.version = version
2561
2562     def properties(self):
2563         return ['suite_name', 'version', 'sources_count', 'binaries_count']
2564
2565     def not_null_constraints(self):
2566         return ['suite_name', 'version']
2567
2568     def __eq__(self, val):
2569         if isinstance(val, str):
2570             return (self.suite_name == val)
2571         # This signals to use the normal comparison operator
2572         return NotImplemented
2573
2574     def __ne__(self, val):
2575         if isinstance(val, str):
2576             return (self.suite_name != val)
2577         # This signals to use the normal comparison operator
2578         return NotImplemented
2579
2580     def details(self):
2581         ret = []
2582         for disp, field in SUITE_FIELDS:
2583             val = getattr(self, field, None)
2584             if val is not None:
2585                 ret.append("%s: %s" % (disp, val))
2586
2587         return "\n".join(ret)
2588
2589     def get_architectures(self, skipsrc=False, skipall=False):
2590         """
2591         Returns list of Architecture objects
2592
2593         @type skipsrc: boolean
2594         @param skipsrc: Whether to skip returning the 'source' architecture entry
2595         (Default False)
2596
2597         @type skipall: boolean
2598         @param skipall: Whether to skip returning the 'all' architecture entry
2599         (Default False)
2600
2601         @rtype: list
2602         @return: list of Architecture objects for the given name (may be empty)
2603         """
2604
2605         q = object_session(self).query(Architecture).with_parent(self)
2606         if skipsrc:
2607             q = q.filter(Architecture.arch_string != 'source')
2608         if skipall:
2609             q = q.filter(Architecture.arch_string != 'all')
2610         return q.order_by(Architecture.arch_string).all()
2611
2612     def get_sources(self, source):
2613         """
2614         Returns a query object representing DBSource that is part of C{suite}.
2615
2616           - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2617
2618         @type source: string
2619         @param source: source package name
2620
2621         @rtype: sqlalchemy.orm.query.Query
2622         @return: a query of DBSource
2623
2624         """
2625
2626         session = object_session(self)
2627         return session.query(DBSource).filter_by(source = source). \
2628             with_parent(self)
2629
2630 __all__.append('Suite')
2631
2632 @session_wrapper
2633 def get_suite(suite, session=None):
2634     """
2635     Returns Suite object for given C{suite name}.
2636
2637     @type suite: string
2638     @param suite: The name of the suite
2639
2640     @type session: Session
2641     @param session: Optional SQLA session object (a temporary one will be
2642     generated if not supplied)
2643
2644     @rtype: Suite
2645     @return: Suite object for the requested suite name (None if not present)
2646     """
2647
2648     q = session.query(Suite).filter_by(suite_name=suite)
2649
2650     try:
2651         return q.one()
2652     except NoResultFound:
2653         return None
2654
2655 __all__.append('get_suite')
2656
2657 ################################################################################
2658
2659 # TODO: should be removed because the implementation is too trivial
2660 @session_wrapper
2661 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2662     """
2663     Returns list of Architecture objects for given C{suite} name
2664
2665     @type suite: str
2666     @param suite: Suite name to search for
2667
2668     @type skipsrc: boolean
2669     @param skipsrc: Whether to skip returning the 'source' architecture entry
2670     (Default False)
2671
2672     @type skipall: boolean
2673     @param skipall: Whether to skip returning the 'all' architecture entry
2674     (Default False)
2675
2676     @type session: Session
2677     @param session: Optional SQL session object (a temporary one will be
2678     generated if not supplied)
2679
2680     @rtype: list
2681     @return: list of Architecture objects for the given name (may be empty)
2682     """
2683
2684     return get_suite(suite, session).get_architectures(skipsrc, skipall)
2685
2686 __all__.append('get_suite_architectures')
2687
2688 ################################################################################
2689
2690 class SuiteSrcFormat(object):
2691     def __init__(self, *args, **kwargs):
2692         pass
2693
2694     def __repr__(self):
2695         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2696
2697 __all__.append('SuiteSrcFormat')
2698
2699 @session_wrapper
2700 def get_suite_src_formats(suite, session=None):
2701     """
2702     Returns list of allowed SrcFormat for C{suite}.
2703
2704     @type suite: str
2705     @param suite: Suite name to search for
2706
2707     @type session: Session
2708     @param session: Optional SQL session object (a temporary one will be
2709     generated if not supplied)
2710
2711     @rtype: list
2712     @return: the list of allowed source formats for I{suite}
2713     """
2714
2715     q = session.query(SrcFormat)
2716     q = q.join(SuiteSrcFormat)
2717     q = q.join(Suite).filter_by(suite_name=suite)
2718     q = q.order_by('format_name')
2719
2720     return q.all()
2721
2722 __all__.append('get_suite_src_formats')
2723
2724 ################################################################################
2725
2726 class Uid(ORMObject):
2727     def __init__(self, uid = None, name = None):
2728         self.uid = uid
2729         self.name = name
2730
2731     def __eq__(self, val):
2732         if isinstance(val, str):
2733             return (self.uid == val)
2734         # This signals to use the normal comparison operator
2735         return NotImplemented
2736
2737     def __ne__(self, val):
2738         if isinstance(val, str):
2739             return (self.uid != val)
2740         # This signals to use the normal comparison operator
2741         return NotImplemented
2742
2743     def properties(self):
2744         return ['uid', 'name', 'fingerprint']
2745
2746     def not_null_constraints(self):
2747         return ['uid']
2748
2749 __all__.append('Uid')
2750
2751 @session_wrapper
2752 def get_or_set_uid(uidname, session=None):
2753     """
2754     Returns uid object for given uidname.
2755
2756     If no matching uidname is found, a row is inserted.
2757
2758     @type uidname: string
2759     @param uidname: The uid to add
2760
2761     @type session: SQLAlchemy
2762     @param session: Optional SQL session object (a temporary one will be
2763     generated if not supplied).  If not passed, a commit will be performed at
2764     the end of the function, otherwise the caller is responsible for commiting.
2765
2766     @rtype: Uid
2767     @return: the uid object for the given uidname
2768     """
2769
2770     q = session.query(Uid).filter_by(uid=uidname)
2771
2772     try:
2773         ret = q.one()
2774     except NoResultFound:
2775         uid = Uid()
2776         uid.uid = uidname
2777         session.add(uid)
2778         session.commit_or_flush()
2779         ret = uid
2780
2781     return ret
2782
2783 __all__.append('get_or_set_uid')
2784
2785 @session_wrapper
2786 def get_uid_from_fingerprint(fpr, session=None):
2787     q = session.query(Uid)
2788     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2789
2790     try:
2791         return q.one()
2792     except NoResultFound:
2793         return None
2794
2795 __all__.append('get_uid_from_fingerprint')
2796
2797 ################################################################################
2798
2799 class UploadBlock(object):
2800     def __init__(self, *args, **kwargs):
2801         pass
2802
2803     def __repr__(self):
2804         return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2805
2806 __all__.append('UploadBlock')
2807
2808 ################################################################################
2809
2810 class DBConn(object):
2811     """
2812     database module init.
2813     """
2814     __shared_state = {}
2815
2816     def __init__(self, *args, **kwargs):
2817         self.__dict__ = self.__shared_state
2818
2819         if not getattr(self, 'initialised', False):
2820             self.initialised = True
2821             self.debug = kwargs.has_key('debug')
2822             self.__createconn()
2823
2824     def __setuptables(self):
2825         tables_with_primary = (
2826             'architecture',
2827             'archive',
2828             'bin_associations',
2829             'binaries',
2830             'binary_acl',
2831             'binary_acl_map',
2832             'build_queue',
2833             'changelogs_text',
2834             'component',
2835             'config',
2836             'changes_pending_binaries',
2837             'changes_pending_files',
2838             'changes_pending_source',
2839             'dsc_files',
2840             'files',
2841             'fingerprint',
2842             'keyrings',
2843             'keyring_acl_map',
2844             'location',
2845             'maintainer',
2846             'new_comments',
2847             'override_type',
2848             'pending_bin_contents',
2849             'policy_queue',
2850             'priority',
2851             'section',
2852             'source',
2853             'source_acl',
2854             'src_associations',
2855             'src_format',
2856             'src_uploaders',
2857             'suite',
2858             'uid',
2859             'upload_blocks',
2860             # The following tables have primary keys but sqlalchemy
2861             # version 0.5 fails to reflect them correctly with database
2862             # versions before upgrade #41.
2863             #'changes',
2864             #'build_queue_files',
2865         )
2866
2867         tables_no_primary = (
2868             'bin_contents',
2869             'changes_pending_files_map',
2870             'changes_pending_source_files',
2871             'changes_pool_files',
2872             'deb_contents',
2873             'override',
2874             'suite_architectures',
2875             'suite_src_formats',
2876             'suite_build_queue_copy',
2877             'udeb_contents',
2878             # see the comment above
2879             'changes',
2880             'build_queue_files',
2881         )
2882
2883         views = (
2884             'almost_obsolete_all_associations',
2885             'almost_obsolete_src_associations',
2886             'any_associations_source',
2887             'bin_assoc_by_arch',
2888             'bin_associations_binaries',
2889             'binaries_suite_arch',
2890             'binfiles_suite_component_arch',
2891             'changelogs',
2892             'file_arch_suite',
2893             'newest_all_associations',
2894             'newest_any_associations',
2895             'newest_source',
2896             'newest_src_association',
2897             'obsolete_all_associations',
2898             'obsolete_any_associations',
2899             'obsolete_any_by_all_associations',
2900             'obsolete_src_associations',
2901             'source_suite',
2902             'src_associations_bin',
2903             'src_associations_src',
2904             'suite_arch_by_name',
2905         )
2906
2907         # Sqlalchemy version 0.5 fails to reflect the SERIAL type
2908         # correctly and that is why we have to use a workaround. It can
2909         # be removed as soon as we switch to version 0.6.
2910         for table_name in tables_with_primary:
2911             table = Table(table_name, self.db_meta, \
2912                 Column('id', Integer, primary_key = True), \
2913                 autoload=True, useexisting=True)
2914             setattr(self, 'tbl_%s' % table_name, table)
2915
2916         for table_name in tables_no_primary:
2917             table = Table(table_name, self.db_meta, autoload=True)
2918             setattr(self, 'tbl_%s' % table_name, table)
2919
2920         for view_name in views:
2921             view = Table(view_name, self.db_meta, autoload=True)
2922             setattr(self, 'view_%s' % view_name, view)
2923
2924     def __setupmappers(self):
2925         mapper(Architecture, self.tbl_architecture,
2926             properties = dict(arch_id = self.tbl_architecture.c.id,
2927                suites = relation(Suite, secondary=self.tbl_suite_architectures,
2928                    order_by='suite_name',
2929                    backref=backref('architectures', order_by='arch_string'))),
2930             extension = validator)
2931
2932         mapper(Archive, self.tbl_archive,
2933                properties = dict(archive_id = self.tbl_archive.c.id,
2934                                  archive_name = self.tbl_archive.c.name))
2935
2936         mapper(PendingBinContents, self.tbl_pending_bin_contents,
2937                properties = dict(contents_id =self.tbl_pending_bin_contents.c.id,
2938                                  filename = self.tbl_pending_bin_contents.c.filename,
2939                                  package = self.tbl_pending_bin_contents.c.package,
2940                                  version = self.tbl_pending_bin_contents.c.version,
2941                                  arch = self.tbl_pending_bin_contents.c.arch,
2942                                  otype = self.tbl_pending_bin_contents.c.type))
2943
2944         mapper(DebContents, self.tbl_deb_contents,
2945                properties = dict(binary_id=self.tbl_deb_contents.c.binary_id,
2946                                  package=self.tbl_deb_contents.c.package,
2947                                  suite=self.tbl_deb_contents.c.suite,
2948                                  arch=self.tbl_deb_contents.c.arch,
2949                                  section=self.tbl_deb_contents.c.section,
2950                                  filename=self.tbl_deb_contents.c.filename))
2951
2952         mapper(UdebContents, self.tbl_udeb_contents,
2953                properties = dict(binary_id=self.tbl_udeb_contents.c.binary_id,
2954                                  package=self.tbl_udeb_contents.c.package,
2955                                  suite=self.tbl_udeb_contents.c.suite,
2956                                  arch=self.tbl_udeb_contents.c.arch,
2957                                  section=self.tbl_udeb_contents.c.section,
2958                                  filename=self.tbl_udeb_contents.c.filename))
2959
2960         mapper(BuildQueue, self.tbl_build_queue,
2961                properties = dict(queue_id = self.tbl_build_queue.c.id))
2962
2963         mapper(BuildQueueFile, self.tbl_build_queue_files,
2964                properties = dict(buildqueue = relation(BuildQueue, backref='queuefiles'),
2965                                  poolfile = relation(PoolFile, backref='buildqueueinstances')))
2966
2967         mapper(DBBinary, self.tbl_binaries,
2968                properties = dict(binary_id = self.tbl_binaries.c.id,
2969                                  package = self.tbl_binaries.c.package,
2970                                  version = self.tbl_binaries.c.version,
2971                                  maintainer_id = self.tbl_binaries.c.maintainer,
2972                                  maintainer = relation(Maintainer),
2973                                  source_id = self.tbl_binaries.c.source,
2974                                  source = relation(DBSource, backref='binaries'),
2975                                  arch_id = self.tbl_binaries.c.architecture,
2976                                  architecture = relation(Architecture),
2977                                  poolfile_id = self.tbl_binaries.c.file,
2978                                  poolfile = relation(PoolFile, backref=backref('binary', uselist = False)),
2979                                  binarytype = self.tbl_binaries.c.type,
2980                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2981                                  fingerprint = relation(Fingerprint),
2982                                  install_date = self.tbl_binaries.c.install_date,
2983                                  suites = relation(Suite, secondary=self.tbl_bin_associations,
2984                                      backref=backref('binaries', lazy='dynamic'))),
2985                 extension = validator)
2986
2987         mapper(BinaryACL, self.tbl_binary_acl,
2988                properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2989
2990         mapper(BinaryACLMap, self.tbl_binary_acl_map,
2991                properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2992                                  fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2993                                  architecture = relation(Architecture)))
2994
2995         mapper(Component, self.tbl_component,
2996                properties = dict(component_id = self.tbl_component.c.id,
2997                                  component_name = self.tbl_component.c.name),
2998                extension = validator)
2999
3000         mapper(DBConfig, self.tbl_config,
3001                properties = dict(config_id = self.tbl_config.c.id))
3002
3003         mapper(DSCFile, self.tbl_dsc_files,
3004                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
3005                                  source_id = self.tbl_dsc_files.c.source,
3006                                  source = relation(DBSource),
3007                                  poolfile_id = self.tbl_dsc_files.c.file,
3008                                  poolfile = relation(PoolFile)))
3009
3010         mapper(PoolFile, self.tbl_files,
3011                properties = dict(file_id = self.tbl_files.c.id,
3012                                  filesize = self.tbl_files.c.size,
3013                                  location_id = self.tbl_files.c.location,
3014                                  location = relation(Location,
3015                                      # using lazy='dynamic' in the back
3016                                      # reference because we have A LOT of
3017                                      # files in one location
3018                                      backref=backref('files', lazy='dynamic'))),
3019                 extension = validator)
3020
3021         mapper(Fingerprint, self.tbl_fingerprint,
3022                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
3023                                  uid_id = self.tbl_fingerprint.c.uid,
3024                                  uid = relation(Uid),
3025                                  keyring_id = self.tbl_fingerprint.c.keyring,
3026                                  keyring = relation(Keyring),
3027                                  source_acl = relation(SourceACL),
3028                                  binary_acl = relation(BinaryACL)),
3029                extension = validator)
3030
3031         mapper(Keyring, self.tbl_keyrings,
3032                properties = dict(keyring_name = self.tbl_keyrings.c.name,
3033                                  keyring_id = self.tbl_keyrings.c.id))
3034
3035         mapper(DBChange, self.tbl_changes,
3036                properties = dict(change_id = self.tbl_changes.c.id,
3037                                  poolfiles = relation(PoolFile,
3038                                                       secondary=self.tbl_changes_pool_files,
3039                                                       backref="changeslinks"),
3040                                  seen = self.tbl_changes.c.seen,
3041                                  source = self.tbl_changes.c.source,
3042                                  binaries = self.tbl_changes.c.binaries,
3043                                  architecture = self.tbl_changes.c.architecture,
3044                                  distribution = self.tbl_changes.c.distribution,
3045                                  urgency = self.tbl_changes.c.urgency,
3046                                  maintainer = self.tbl_changes.c.maintainer,
3047                                  changedby = self.tbl_changes.c.changedby,
3048                                  date = self.tbl_changes.c.date,
3049                                  version = self.tbl_changes.c.version,
3050                                  files = relation(ChangePendingFile,
3051                                                   secondary=self.tbl_changes_pending_files_map,
3052                                                   backref="changesfile"),
3053                                  in_queue_id = self.tbl_changes.c.in_queue,
3054                                  in_queue = relation(PolicyQueue,
3055                                                      primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
3056                                  approved_for_id = self.tbl_changes.c.approved_for))
3057
3058         mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
3059                properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
3060
3061         mapper(ChangePendingFile, self.tbl_changes_pending_files,
3062                properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id,
3063                                  filename = self.tbl_changes_pending_files.c.filename,
3064                                  size = self.tbl_changes_pending_files.c.size,
3065                                  md5sum = self.tbl_changes_pending_files.c.md5sum,
3066                                  sha1sum = self.tbl_changes_pending_files.c.sha1sum,
3067                                  sha256sum = self.tbl_changes_pending_files.c.sha256sum))
3068
3069         mapper(ChangePendingSource, self.tbl_changes_pending_source,
3070                properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
3071                                  change = relation(DBChange),
3072                                  maintainer = relation(Maintainer,
3073                                                        primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
3074                                  changedby = relation(Maintainer,
3075                                                       primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
3076                                  fingerprint = relation(Fingerprint),
3077                                  source_files = relation(ChangePendingFile,
3078                                                          secondary=self.tbl_changes_pending_source_files,
3079                                                          backref="pending_sources")))
3080
3081
3082         mapper(KeyringACLMap, self.tbl_keyring_acl_map,
3083                properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
3084                                  keyring = relation(Keyring, backref="keyring_acl_map"),
3085                                  architecture = relation(Architecture)))
3086
3087         mapper(Location, self.tbl_location,
3088                properties = dict(location_id = self.tbl_location.c.id,
3089                                  component_id = self.tbl_location.c.component,
3090                                  component = relation(Component, \
3091                                      backref=backref('location', uselist = False)),
3092                                  archive_id = self.tbl_location.c.archive,
3093                                  archive = relation(Archive),
3094                                  # FIXME: the 'type' column is old cruft and
3095                                  # should be removed in the future.
3096                                  archive_type = self.tbl_location.c.type),
3097                extension = validator)
3098
3099         mapper(Maintainer, self.tbl_maintainer,
3100                properties = dict(maintainer_id = self.tbl_maintainer.c.id,
3101                    maintains_sources = relation(DBSource, backref='maintainer',
3102                        primaryjoin=(self.tbl_maintainer.c.id==self.tbl_source.c.maintainer)),
3103                    changed_sources = relation(DBSource, backref='changedby',
3104                        primaryjoin=(self.tbl_maintainer.c.id==self.tbl_source.c.changedby))),
3105                 extension = validator)
3106
3107         mapper(NewComment, self.tbl_new_comments,
3108                properties = dict(comment_id = self.tbl_new_comments.c.id))
3109
3110         mapper(Override, self.tbl_override,
3111                properties = dict(suite_id = self.tbl_override.c.suite,
3112                                  suite = relation(Suite),
3113                                  package = self.tbl_override.c.package,
3114                                  component_id = self.tbl_override.c.component,
3115                                  component = relation(Component),
3116                                  priority_id = self.tbl_override.c.priority,
3117                                  priority = relation(Priority),
3118                                  section_id = self.tbl_override.c.section,
3119                                  section = relation(Section),
3120                                  overridetype_id = self.tbl_override.c.type,
3121                                  overridetype = relation(OverrideType)))
3122
3123         mapper(OverrideType, self.tbl_override_type,
3124                properties = dict(overridetype = self.tbl_override_type.c.type,
3125                                  overridetype_id = self.tbl_override_type.c.id))
3126
3127         mapper(PolicyQueue, self.tbl_policy_queue,
3128                properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
3129
3130         mapper(Priority, self.tbl_priority,
3131                properties = dict(priority_id = self.tbl_priority.c.id))
3132
3133         mapper(Section, self.tbl_section,
3134                properties = dict(section_id = self.tbl_section.c.id,
3135                                  section=self.tbl_section.c.section))
3136
3137         mapper(DBSource, self.tbl_source,
3138                properties = dict(source_id = self.tbl_source.c.id,
3139                                  version = self.tbl_source.c.version,
3140                                  maintainer_id = self.tbl_source.c.maintainer,
3141                                  poolfile_id = self.tbl_source.c.file,
3142                                  poolfile = relation(PoolFile, backref=backref('source', uselist = False)),
3143                                  fingerprint_id = self.tbl_source.c.sig_fpr,
3144                                  fingerprint = relation(Fingerprint),
3145                                  changedby_id = self.tbl_source.c.changedby,
3146                                  srcfiles = relation(DSCFile,
3147                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
3148                                  suites = relation(Suite, secondary=self.tbl_src_associations,
3149                                      backref=backref('sources', lazy='dynamic')),
3150                                  srcuploaders = relation(SrcUploader)),
3151                extension = validator)
3152
3153         mapper(SourceACL, self.tbl_source_acl,
3154                properties = dict(source_acl_id = self.tbl_source_acl.c.id))
3155
3156         mapper(SrcFormat, self.tbl_src_format,
3157                properties = dict(src_format_id = self.tbl_src_format.c.id,
3158                                  format_name = self.tbl_src_format.c.format_name))
3159
3160         mapper(SrcUploader, self.tbl_src_uploaders,
3161                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
3162                                  source_id = self.tbl_src_uploaders.c.source,
3163                                  source = relation(DBSource,
3164                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
3165                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
3166                                  maintainer = relation(Maintainer,
3167                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
3168
3169         mapper(Suite, self.tbl_suite,
3170                properties = dict(suite_id = self.tbl_suite.c.id,
3171                                  policy_queue = relation(PolicyQueue),
3172                                  copy_queues = relation(BuildQueue,
3173                                      secondary=self.tbl_suite_build_queue_copy)),
3174                 extension = validator)
3175
3176         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3177                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3178                                  suite = relation(Suite, backref='suitesrcformats'),
3179                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
3180                                  src_format = relation(SrcFormat)))
3181
3182         mapper(Uid, self.tbl_uid,
3183                properties = dict(uid_id = self.tbl_uid.c.id,
3184                                  fingerprint = relation(Fingerprint)),
3185                extension = validator)
3186
3187         mapper(UploadBlock, self.tbl_upload_blocks,
3188                properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3189                                  fingerprint = relation(Fingerprint, backref="uploadblocks"),
3190                                  uid = relation(Uid, backref="uploadblocks")))
3191
3192     ## Connection functions
3193     def __createconn(self):
3194         from config import Config
3195         cnf = Config()
3196         if cnf["DB::Host"]:
3197             # TCP/IP
3198             connstr = "postgres://%s" % cnf["DB::Host"]
3199             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3200                 connstr += ":%s" % cnf["DB::Port"]
3201             connstr += "/%s" % cnf["DB::Name"]
3202         else:
3203             # Unix Socket
3204             connstr = "postgres:///%s" % cnf["DB::Name"]
3205             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3206                 connstr += "?port=%s" % cnf["DB::Port"]
3207
3208         self.db_pg   = create_engine(connstr, echo=self.debug)
3209         self.db_meta = MetaData()
3210         self.db_meta.bind = self.db_pg
3211         self.db_smaker = sessionmaker(bind=self.db_pg,
3212                                       autoflush=True,
3213                                       autocommit=False)
3214
3215         self.__setuptables()
3216         self.__setupmappers()
3217
3218     def session(self):
3219         return self.db_smaker()
3220
3221 __all__.append('DBConn')
3222
3223