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