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