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