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