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