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