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