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