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