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