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