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