]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
add support routines needed for queue.py
[dak.git] / daklib / dbconn.py
1 #!/usr/bin/python
2
3 """ DB access class
4
5 @contact: Debian FTPMaster <ftpmaster@debian.org>
6 @copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
7 @copyright: 2008-2009  Mark Hymers <mhy@debian.org>
8 @copyright: 2009  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 psycopg2
38 import traceback
39
40 from sqlalchemy import create_engine, Table, MetaData, select
41 from sqlalchemy.orm import sessionmaker, mapper, relation
42
43 # Don't remove this, we re-export the exceptions to scripts which import us
44 from sqlalchemy.exc import *
45
46 # Only import Config until Queue stuff is changed to store its config
47 # in the database
48 from config import Config
49 from singleton import Singleton
50 from textutils import fix_maintainer
51
52 ################################################################################
53
54 __all__ = ['IntegrityError', 'SQLAlchemyError']
55
56 ################################################################################
57
58 class Architecture(object):
59     def __init__(self, *args, **kwargs):
60         pass
61
62     def __repr__(self):
63         return '<Architecture %s>' % self.arch_string
64
65 __all__.append('Architecture')
66
67 def get_architecture(architecture, session=None):
68     """
69     Returns database id for given C{architecture}.
70
71     @type architecture: string
72     @param architecture: The name of the architecture
73
74     @type session: Session
75     @param session: Optional SQLA session object (a temporary one will be
76     generated if not supplied)
77
78     @rtype: Architecture
79     @return: Architecture object for the given arch (None if not present)
80
81     """
82     if session is None:
83         session = DBConn().session()
84     q = session.query(Architecture).filter_by(arch_string=architecture)
85     if q.count() == 0:
86         return None
87     return q.one()
88
89 __all__.append('get_architecture')
90
91 def get_architecture_suites(architecture, session=None):
92     """
93     Returns list of Suite objects for given C{architecture} name
94
95     @type source: str
96     @param source: Architecture name to search for
97
98     @type session: Session
99     @param session: Optional SQL session object (a temporary one will be
100     generated if not supplied)
101
102     @rtype: list
103     @return: list of Suite objects for the given name (may be empty)
104     """
105
106     if session is None:
107         session = DBConn().session()
108
109     q = session.query(Suite)
110     q = q.join(SuiteArchitecture)
111     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
112     return q.all()
113
114 __all__.append('get_architecture_suites')
115
116 ################################################################################
117
118 class Archive(object):
119     def __init__(self, *args, **kwargs):
120         pass
121
122     def __repr__(self):
123         return '<Archive %s>' % self.name
124
125 __all__.append('Archive')
126
127 def get_archive(archive, session=None):
128     """
129     returns database id for given c{archive}.
130
131     @type archive: string
132     @param archive: the name of the arhive
133
134     @type session: Session
135     @param session: Optional SQLA session object (a temporary one will be
136     generated if not supplied)
137
138     @rtype: Archive
139     @return: Archive object for the given name (None if not present)
140
141     """
142     archive = archive.lower()
143     if session is None:
144         session = DBConn().session()
145     q = session.query(Archive).filter_by(archive_name=archive)
146     if q.count() == 0:
147         return None
148     return q.one()
149
150 __all__.append('get_archive')
151
152 ################################################################################
153
154 class BinAssociation(object):
155     def __init__(self, *args, **kwargs):
156         pass
157
158     def __repr__(self):
159         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
160
161 __all__.append('BinAssociation')
162
163 ################################################################################
164
165 class DBBinary(object):
166     def __init__(self, *args, **kwargs):
167         pass
168
169     def __repr__(self):
170         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
171
172 __all__.append('DBBinary')
173
174 def get_binary_from_id(id, session=None):
175     """
176     Returns DBBinary object for given C{id}
177
178     @type id: int
179     @param id: Id of the required binary
180
181     @type session: Session
182     @param session: Optional SQLA session object (a temporary one will be
183     generated if not supplied)
184
185     @rtype: DBBinary
186     @return: DBBinary object for the given binary (None if not present)
187     """
188     if session is None:
189         session = DBConn().session()
190     q = session.query(DBBinary).filter_by(binary_id=id)
191     if q.count() == 0:
192         return None
193     return q.one()
194
195 __all__.append('get_binary_from_id')
196
197 def get_binaries_from_name(package, session=None):
198     """
199     Returns list of DBBinary objects for given C{package} name
200
201     @type package: str
202     @param package: DBBinary package 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 DBBinary objects for the given name (may be empty)
210     """
211     if session is None:
212         session = DBConn().session()
213     return session.query(DBBinary).filter_by(package=package).all()
214
215 __all__.append('get_binaries_from_name')
216
217 def get_binary_from_name_suite(package, suitename, session=None):
218     ### For dak examine-package
219     ### XXX: Doesn't use object API yet
220     if session is None:
221         session = DBConn().session()
222
223     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
224              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
225              WHERE b.package=:package
226                AND b.file = fi.id
227                AND fi.location = l.id
228                AND l.component = c.id
229                AND ba.bin=b.id
230                AND ba.suite = su.id
231                AND su.suite_name=:suitename
232           ORDER BY b.version DESC"""
233
234     return session.execute(sql, {'package': package, 'suitename': suitename})
235
236 __all__.append('get_binary_from_name_suite')
237
238 def get_binary_components(package, suitename, arch, session=None):
239 # Check for packages that have moved from one component to another
240     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
241     WHERE b.package=:package AND s.suite_name=:suitename
242       AND (a.arch_string = :arch OR a.arch_string = 'all')
243       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
244       AND f.location = l.id
245       AND l.component = c.id
246       AND b.file = f.id"""
247
248     vals = {'package': package, 'suitename': suitename, 'arch': arch}
249
250     if session is None:
251         session = DBConn().session()
252     return session.execute(query, vals)
253
254 __all__.append('get_binary_components')
255 ################################################################################
256
257 class Component(object):
258     def __init__(self, *args, **kwargs):
259         pass
260
261     def __repr__(self):
262         return '<Component %s>' % self.component_name
263
264
265 __all__.append('Component')
266
267 def get_component(component, session=None):
268     """
269     Returns database id for given C{component}.
270
271     @type component: string
272     @param component: The name of the override type
273
274     @rtype: int
275     @return: the database id for the given component
276
277     """
278     component = component.lower()
279     if session is None:
280         session = DBConn().session()
281     q = session.query(Component).filter_by(component_name=component)
282     if q.count() == 0:
283         return None
284     return q.one()
285
286 __all__.append('get_component')
287
288 ################################################################################
289
290 class DBConfig(object):
291     def __init__(self, *args, **kwargs):
292         pass
293
294     def __repr__(self):
295         return '<DBConfig %s>' % self.name
296
297 __all__.append('DBConfig')
298
299 ################################################################################
300
301 class ContentFilename(object):
302     def __init__(self, *args, **kwargs):
303         pass
304
305     def __repr__(self):
306         return '<ContentFilename %s>' % self.filename
307
308 __all__.append('ContentFilename')
309
310 def get_or_set_contents_file_id(filename, session=None):
311     """
312     Returns database id for given filename.
313
314     If no matching file is found, a row is inserted.
315
316     @type filename: string
317     @param filename: The filename
318     @type session: SQLAlchemy
319     @param session: Optional SQL session object (a temporary one will be
320     generated if not supplied).  If not passed, a commit will be performed at
321     the end of the function, otherwise the caller is responsible for commiting.
322
323     @rtype: int
324     @return: the database id for the given component
325     """
326     privatetrans = False
327     if session is None:
328         session = DBConn().session()
329         privatetrans = True
330
331     try:
332         q = session.query(ContentFilename).filter_by(filename=filename)
333         if q.count() < 1:
334             cf = ContentFilename()
335             cf.filename = filename
336             session.add(cf)
337             if privatetrans:
338                 session.commit()
339             return cf.cafilename_id
340         else:
341             return q.one().cafilename_id
342
343     except:
344         traceback.print_exc()
345         raise
346
347 __all__.append('get_or_set_contents_file_id')
348
349 def get_contents(suite, overridetype, section=None, session=None):
350     """
351     Returns contents for a suite / overridetype combination, limiting
352     to a section if not None.
353
354     @type suite: Suite
355     @param suite: Suite object
356
357     @type overridetype: OverrideType
358     @param overridetype: OverrideType object
359
360     @type section: Section
361     @param section: Optional section object to limit results to
362
363     @type session: SQLAlchemy
364     @param session: Optional SQL session object (a temporary one will be
365     generated if not supplied)
366
367     @rtype: ResultsProxy
368     @return: ResultsProxy object set up to return tuples of (filename, section,
369     package, arch_id)
370     """
371
372     if session is None:
373         session = DBConn().session()
374
375     # find me all of the contents for a given suite
376     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
377                             s.section,
378                             b.package,
379                             b.architecture
380                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
381                    JOIN content_file_names n ON (c.filename=n.id)
382                    JOIN binaries b ON (b.id=c.binary_pkg)
383                    JOIN override o ON (o.package=b.package)
384                    JOIN section s ON (s.id=o.section)
385                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
386                    AND b.type=:overridetypename"""
387
388     vals = {'suiteid': suite.suite_id,
389             'overridetypeid': overridetype.overridetype_id,
390             'overridetypename': overridetype.overridetype}
391
392     if section is not None:
393         contents_q += " AND s.id = :sectionid"
394         vals['sectionid'] = section.section_id
395
396     contents_q += " ORDER BY fn"
397
398     return session.execute(contents_q, vals)
399
400 __all__.append('get_contents')
401
402 ################################################################################
403
404 class ContentFilepath(object):
405     def __init__(self, *args, **kwargs):
406         pass
407
408     def __repr__(self):
409         return '<ContentFilepath %s>' % self.filepath
410
411 __all__.append('ContentFilepath')
412
413 def get_or_set_contents_path_id(filepath, session):
414     """
415     Returns database id for given path.
416
417     If no matching file is found, a row is inserted.
418
419     @type filename: string
420     @param filename: The filepath
421     @type session: SQLAlchemy
422     @param session: Optional SQL session object (a temporary one will be
423     generated if not supplied).  If not passed, a commit will be performed at
424     the end of the function, otherwise the caller is responsible for commiting.
425
426     @rtype: int
427     @return: the database id for the given path
428     """
429     privatetrans = False
430     if session is None:
431         session = DBConn().session()
432         privatetrans = True
433
434     try:
435         q = session.query(ContentFilepath).filter_by(filepath=filepath)
436         if q.count() < 1:
437             cf = ContentFilepath()
438             cf.filepath = filepath
439             session.add(cf)
440             if privatetrans:
441                 session.commit()
442             return cf.cafilepath_id
443         else:
444             return q.one().cafilepath_id
445
446     except:
447         traceback.print_exc()
448         raise
449
450 __all__.append('get_or_set_contents_path_id')
451
452 ################################################################################
453
454 class ContentAssociation(object):
455     def __init__(self, *args, **kwargs):
456         pass
457
458     def __repr__(self):
459         return '<ContentAssociation %s>' % self.ca_id
460
461 __all__.append('ContentAssociation')
462
463 def insert_content_paths(binary_id, fullpaths, session=None):
464     """
465     Make sure given path is associated with given binary id
466
467     @type binary_id: int
468     @param binary_id: the id of the binary
469     @type fullpaths: list
470     @param fullpaths: the list of paths of the file being associated with the binary
471     @type session: SQLAlchemy session
472     @param session: Optional SQLAlchemy session.  If this is passed, the caller
473     is responsible for ensuring a transaction has begun and committing the
474     results or rolling back based on the result code.  If not passed, a commit
475     will be performed at the end of the function, otherwise the caller is
476     responsible for commiting.
477
478     @return: True upon success
479     """
480
481     privatetrans = False
482
483     if session is None:
484         session = DBConn().session()
485         privatetrans = True
486
487     try:
488         for fullpath in fullpaths:
489             (path, file) = os.path.split(fullpath)
490
491             # Get the necessary IDs ...
492             ca = ContentAssociation()
493             ca.binary_id = binary_id
494             ca.filename_id = get_or_set_contents_file_id(file)
495             ca.filepath_id = get_or_set_contents_path_id(path)
496             session.add(ca)
497
498         # Only commit if we set up the session ourself
499         if privatetrans:
500             session.commit()
501
502         return True
503     except:
504         traceback.print_exc()
505
506         # Only rollback if we set up the session ourself
507         if privatetrans:
508             session.rollback()
509
510         return False
511
512 __all__.append('insert_content_paths')
513
514 ################################################################################
515
516 class DSCFile(object):
517     def __init__(self, *args, **kwargs):
518         pass
519
520     def __repr__(self):
521         return '<DSCFile %s>' % self.dscfile_id
522
523 __all__.append('DSCFile')
524
525 ################################################################################
526
527 class PoolFile(object):
528     def __init__(self, *args, **kwargs):
529         pass
530
531     def __repr__(self):
532         return '<PoolFile %s>' % self.filename
533
534 __all__.append('PoolFile')
535
536 def get_poolfile_by_name(filename, location_id=None, session=None):
537     """
538     Returns an array of PoolFile objects for the given filename and
539     (optionally) location_id
540
541     @type filename: string
542     @param filename: the filename of the file to check against the DB
543
544     @type location_id: int
545     @param location_id: the id of the location to look in (optional)
546
547     @rtype: array
548     @return: array of PoolFile objects
549     """
550
551     if session is not None:
552         session = DBConn().session()
553
554     q = session.query(PoolFile).filter_by(filename=filename)
555
556     if location_id is not None:
557         q = q.join(Location).filter_by(location_id=location_id)
558
559     return q.all()
560
561 __all__.append('get_poolfile_by_name')
562
563 def get_poolfile_like_name(filename, session=None):
564     """
565     Returns an array of PoolFile objects which are like the given name
566
567     @type filename: string
568     @param filename: the filename of the file to check against the DB
569
570     @rtype: array
571     @return: array of PoolFile objects
572     """
573
574     if session is not None:
575         session = DBConn().session()
576
577     # TODO: There must be a way of properly using bind parameters with %FOO%
578     q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
579
580     return q.all()
581
582 __all__.append('get_poolfile_like_name')
583
584 ################################################################################
585
586 class Fingerprint(object):
587     def __init__(self, *args, **kwargs):
588         pass
589
590     def __repr__(self):
591         return '<Fingerprint %s>' % self.fingerprint
592
593 __all__.append('Fingerprint')
594
595 ################################################################################
596
597 class Keyring(object):
598     def __init__(self, *args, **kwargs):
599         pass
600
601     def __repr__(self):
602         return '<Keyring %s>' % self.keyring_name
603
604 __all__.append('Keyring')
605
606 ################################################################################
607
608 class Location(object):
609     def __init__(self, *args, **kwargs):
610         pass
611
612     def __repr__(self):
613         return '<Location %s (%s)>' % (self.path, self.location_id)
614
615 __all__.append('Location')
616
617 def get_location(location, component=None, archive=None, session=None):
618     """
619     Returns Location object for the given combination of location, component
620     and archive
621
622     @type location: string
623     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
624
625     @type component: string
626     @param component: the component name (if None, no restriction applied)
627
628     @type archive: string
629     @param archive_id: the archive name (if None, no restriction applied)
630
631     @rtype: Location / None
632     @return: Either a Location object or None if one can't be found
633     """
634
635     if session is None:
636         session = DBConn().session()
637
638     q = session.query(Location).filter_by(path=location)
639
640     if archive is not None:
641         q = q.join(Archive).filter_by(archive_name=archive)
642
643     if component is not None:
644         q = q.join(Component).filter_by(component_name=component)
645
646     if q.count() < 1:
647         return None
648     else:
649         return q.one()
650
651 __all__.append('get_location')
652
653 ################################################################################
654
655 class Maintainer(object):
656     def __init__(self, *args, **kwargs):
657         pass
658
659     def __repr__(self):
660         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
661
662     def get_split_maintainer(self):
663         if not hasattr(self, 'name') or self.name is None:
664             return ('', '', '', '')
665
666         return fix_maintainer(self.name.strip())
667
668 __all__.append('Maintainer')
669
670 ################################################################################
671
672 class Override(object):
673     def __init__(self, *args, **kwargs):
674         pass
675
676     def __repr__(self):
677         return '<Override %s (%s)>' % (self.package, self.suite_id)
678
679 __all__.append('Override')
680
681 def get_override(package, suite=None, component=None, overridetype=None, session=None):
682     """
683     Returns Override object for the given parameters
684
685     @type package: string
686     @param package: The name of the package
687
688     @type suite: string, list or None
689     @param suite: The name of the suite (or suites if a list) to limit to.  If
690                   None, don't limit.  Defaults to None.
691
692     @type component: string, list or None
693     @param component: The name of the component (or components if a list) to
694                       limit to.  If None, don't limit.  Defaults to None.
695
696     @type overridetype: string, list or None
697     @param overridetype: The name of the overridetype (or overridetypes if a list) to
698                          limit to.  If None, don't limit.  Defaults to None.
699
700     @type session: Session
701     @param session: Optional SQLA session object (a temporary one will be
702     generated if not supplied)
703
704     @rtype: list
705     @return: A (possibly empty) list of Override objects will be returned
706
707     """
708     if session is None:
709         session = DBConn().session()
710
711     q = session.query(Override)
712     q = q.filter_by(package=package)
713
714     if suite is not None:
715         if not isinstance(suite, list): suite = [suite]
716         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
717
718     if component is not None:
719         if not isinstance(component, list): component = [component]
720         q = q.join(Component).filter(Component.component_name.in_(component))
721
722     if overridetype is not None:
723         if not isinstance(overridetype, list): overridetype = [overridetype]
724         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
725
726     return q.all()
727
728 __all__.append('get_override')
729
730
731 ################################################################################
732
733 class OverrideType(object):
734     def __init__(self, *args, **kwargs):
735         pass
736
737     def __repr__(self):
738         return '<OverrideType %s>' % self.overridetype
739
740 __all__.append('OverrideType')
741
742 def get_override_type(override_type, session=None):
743     """
744     Returns OverrideType object for given C{override type}.
745
746     @type override_type: string
747     @param override_type: The name of the override type
748
749     @type session: Session
750     @param session: Optional SQLA session object (a temporary one will be
751     generated if not supplied)
752
753     @rtype: int
754     @return: the database id for the given override type
755
756     """
757     if session is None:
758         session = DBConn().session()
759     q = session.query(OverrideType).filter_by(overridetype=override_type)
760     if q.count() == 0:
761         return None
762     return q.one()
763
764 __all__.append('get_override_type')
765
766 ################################################################################
767
768 class PendingContentAssociation(object):
769     def __init__(self, *args, **kwargs):
770         pass
771
772     def __repr__(self):
773         return '<PendingContentAssociation %s>' % self.pca_id
774
775 __all__.append('PendingContentAssociation')
776
777 def insert_pending_content_paths(package, fullpaths, session=None):
778     """
779     Make sure given paths are temporarily associated with given
780     package
781
782     @type package: dict
783     @param package: the package to associate with should have been read in from the binary control file
784     @type fullpaths: list
785     @param fullpaths: the list of paths of the file being associated with the binary
786     @type session: SQLAlchemy session
787     @param session: Optional SQLAlchemy session.  If this is passed, the caller
788     is responsible for ensuring a transaction has begun and committing the
789     results or rolling back based on the result code.  If not passed, a commit
790     will be performed at the end of the function
791
792     @return: True upon success, False if there is a problem
793     """
794
795     privatetrans = False
796
797     if session is None:
798         session = DBConn().session()
799         privatetrans = True
800
801     try:
802         arch = get_architecture(package['Architecture'], session)
803         arch_id = arch.arch_id
804
805         # Remove any already existing recorded files for this package
806         q = session.query(PendingContentAssociation)
807         q = q.filter_by(package=package['Package'])
808         q = q.filter_by(version=package['Version'])
809         q = q.filter_by(architecture=arch_id)
810         q.delete()
811
812         # Insert paths
813         for fullpath in fullpaths:
814             (path, file) = os.path.split(fullpath)
815
816             if path.startswith( "./" ):
817                 path = path[2:]
818
819             pca = PendingContentAssociation()
820             pca.package = package['Package']
821             pca.version = package['Version']
822             pca.filename_id = get_or_set_contents_file_id(file, session)
823             pca.filepath_id = get_or_set_contents_path_id(path, session)
824             pca.architecture = arch_id
825             session.add(pca)
826
827         # Only commit if we set up the session ourself
828         if privatetrans:
829             session.commit()
830
831         return True
832     except:
833         traceback.print_exc()
834
835         # Only rollback if we set up the session ourself
836         if privatetrans:
837             session.rollback()
838
839         return False
840
841 __all__.append('insert_pending_content_paths')
842
843 ################################################################################
844
845 class Priority(object):
846     def __init__(self, *args, **kwargs):
847         pass
848
849     def __repr__(self):
850         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
851
852 __all__.append('Priority')
853
854 def get_priority(priority, session=None):
855     """
856     Returns Priority object for given C{priority name}.
857
858     @type priority: string
859     @param priority: The name of the priority
860
861     @type session: Session
862     @param session: Optional SQLA session object (a temporary one will be
863     generated if not supplied)
864
865     @rtype: Priority
866     @return: Priority object for the given priority
867
868     """
869     if session is None:
870         session = DBConn().session()
871     q = session.query(Priority).filter_by(priority=priority)
872     if q.count() == 0:
873         return None
874     return q.one()
875
876 __all__.append('get_priority')
877
878 ################################################################################
879
880 class Queue(object):
881     def __init__(self, *args, **kwargs):
882         pass
883
884     def __repr__(self):
885         return '<Queue %s>' % self.queue_name
886
887     def autobuild_upload(self, changes, srcpath, session=None):
888         """
889         Update queue_build database table used for incoming autobuild support.
890
891         @type changes: Changes
892         @param changes: changes object for the upload to process
893
894         @type srcpath: string
895         @param srcpath: path for the queue file entries/link destinations
896
897         @type session: SQLAlchemy session
898         @param session: Optional SQLAlchemy session.  If this is passed, the
899         caller is responsible for ensuring a transaction has begun and
900         committing the results or rolling back based on the result code.  If
901         not passed, a commit will be performed at the end of the function,
902         otherwise the caller is responsible for commiting.
903
904         @rtype: NoneType or string
905         @return: None if the operation failed, a string describing the error if not
906         """
907
908         localcommit = False
909         if session is None:
910             session = DBConn().session()
911             localcommit = True
912
913         # TODO: Remove by moving queue config into the database
914         conf = Config()
915
916         for suitename in changes.changes["distribution"].keys():
917             # TODO: Move into database as:
918             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
919             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
920             #       This also gets rid of the SecurityQueueBuild hack below
921             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
922                 continue
923
924             # Find suite object
925             s = get_suite(suitename, session)
926             if s is None:
927                 return "INTERNAL ERROR: Could not find suite %s" % suitename
928
929             # TODO: Get from database as above
930             dest_dir = conf["Dir::QueueBuild"]
931
932             # TODO: Move into database as above
933             if conf.FindB("Dinstall::SecurityQueueBuild"):
934                 dest_dir = os.path.join(dest_dir, suitename)
935
936             for file_entry in changes.files.keys():
937                 src = os.path.join(srcpath, file_entry)
938                 dest = os.path.join(dest_dir, file_entry)
939
940                 # TODO: Move into database as above
941                 if Cnf.FindB("Dinstall::SecurityQueueBuild"):
942                     # Copy it since the original won't be readable by www-data
943                     utils.copy(src, dest)
944                 else:
945                     # Create a symlink to it
946                     os.symlink(src, dest)
947
948                 qb = QueueBuild()
949                 qb.suite_id = s.suite_id
950                 qb.queue_id = self.queue_id
951                 qb.filename = dest
952                 qb.in_queue = True
953
954                 session.add(qb)
955
956             # If the .orig.tar.gz is in the pool, create a symlink to
957             # it (if one doesn't already exist)
958             if changes.orig_tar_id:
959                 # Determine the .orig.tar.gz file name
960                 for dsc_file in changes.dsc_files.keys():
961                     if dsc_file.endswith(".orig.tar.gz"):
962                         filename = dsc_file
963
964                 dest = os.path.join(dest_dir, filename)
965
966                 # If it doesn't exist, create a symlink
967                 if not os.path.exists(dest):
968                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
969                                         {'id': changes.orig_tar_id})
970                     res = q.fetchone()
971                     if not res:
972                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
973
974                     src = os.path.join(res[0], res[1])
975                     os.symlink(src, dest)
976
977                     # Add it to the list of packages for later processing by apt-ftparchive
978                     qb = QueueBuild()
979                     qb.suite_id = s.suite_id
980                     qb.queue_id = self.queue_id
981                     qb.filename = dest
982                     qb.in_queue = True
983                     session.add(qb)
984
985                 # If it does, update things to ensure it's not removed prematurely
986                 else:
987                     qb = get_queue_build(dest, suite_id, session)
988                     if qb is None:
989                         qb.in_queue = True
990                         qb.last_used = None
991                         session.add(qb)
992
993         if localcommit:
994             session.commit()
995
996         return None
997
998 __all__.append('Queue')
999
1000 def get_queue(queuename, session=None):
1001     """
1002     Returns Queue object for given C{queue name}.
1003
1004     @type queuename: string
1005     @param queuename: The name of the queue
1006
1007     @type session: Session
1008     @param session: Optional SQLA session object (a temporary one will be
1009     generated if not supplied)
1010
1011     @rtype: Queue
1012     @return: Queue object for the given queue
1013
1014     """
1015     if session is None:
1016         session = DBConn().session()
1017     q = session.query(Queue).filter_by(queue_name=queuename)
1018     if q.count() == 0:
1019         return None
1020     return q.one()
1021
1022 __all__.append('get_queue')
1023
1024 ################################################################################
1025
1026 class QueueBuild(object):
1027     def __init__(self, *args, **kwargs):
1028         pass
1029
1030     def __repr__(self):
1031         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1032
1033 __all__.append('QueueBuild')
1034
1035 def get_queue_build(filename, suite_id, session=None):
1036     """
1037     Returns QueueBuild object for given C{filename} and C{suite id}.
1038
1039     @type filename: string
1040     @param filename: The name of the file
1041
1042     @type suiteid: int
1043     @param suiteid: Suite ID
1044
1045     @type session: Session
1046     @param session: Optional SQLA session object (a temporary one will be
1047     generated if not supplied)
1048
1049     @rtype: Queue
1050     @return: Queue object for the given queue
1051
1052     """
1053     if session is None:
1054         session = DBConn().session()
1055     q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite_id)
1056     if q.count() == 0:
1057         return None
1058     return q.one()
1059
1060 __all__.append('get_queue_build')
1061
1062 ################################################################################
1063
1064 class Section(object):
1065     def __init__(self, *args, **kwargs):
1066         pass
1067
1068     def __repr__(self):
1069         return '<Section %s>' % self.section
1070
1071 __all__.append('Section')
1072
1073 def get_section(section, session=None):
1074     """
1075     Returns Section object for given C{section name}.
1076
1077     @type section: string
1078     @param section: The name of the section
1079
1080     @type session: Session
1081     @param session: Optional SQLA session object (a temporary one will be
1082     generated if not supplied)
1083
1084     @rtype: Section
1085     @return: Section object for the given section name
1086
1087     """
1088     if session is None:
1089         session = DBConn().session()
1090     q = session.query(Section).filter_by(section=section)
1091     if q.count() == 0:
1092         return None
1093     return q.one()
1094
1095 __all__.append('get_section')
1096
1097 ################################################################################
1098
1099 class DBSource(object):
1100     def __init__(self, *args, **kwargs):
1101         pass
1102
1103     def __repr__(self):
1104         return '<DBSource %s (%s)>' % (self.source, self.version)
1105
1106 __all__.append('DBSource')
1107
1108 def source_exists(source, source_version, suites = ["any"], session=None):
1109     """
1110     Ensure that source exists somewhere in the archive for the binary
1111     upload being processed.
1112       1. exact match     => 1.0-3
1113       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1114
1115     @type package: string
1116     @param package: package source name
1117
1118     @type source_version: string
1119     @param source_version: expected source version
1120
1121     @type suites: list
1122     @param suites: list of suites to check in, default I{any}
1123
1124     @type session: Session
1125     @param session: Optional SQLA session object (a temporary one will be
1126     generated if not supplied)
1127
1128     @rtype: int
1129     @return: returns 1 if a source with expected version is found, otherwise 0
1130
1131     """
1132
1133     if session is None:
1134         session = DBConn().session()
1135
1136     cnf = Config()
1137
1138     for suite in suites:
1139         q = session.query(DBSource).filter_by(source=source)
1140         if suite != "any":
1141             # source must exist in suite X, or in some other suite that's
1142             # mapped to X, recursively... silent-maps are counted too,
1143             # unreleased-maps aren't.
1144             maps = cnf.ValueList("SuiteMappings")[:]
1145             maps.reverse()
1146             maps = [ m.split() for m in maps ]
1147             maps = [ (x[1], x[2]) for x in maps
1148                             if x[0] == "map" or x[0] == "silent-map" ]
1149             s = [suite]
1150             for x in maps:
1151                 if x[1] in s and x[0] not in s:
1152                     s.append(x[0])
1153
1154             q = q.join(SrcAssociation).join(Suite)
1155             q = q.filter(Suite.suite_name.in_(s))
1156
1157         # Reduce the query results to a list of version numbers
1158         ql = [ j.version for j in q.all() ]
1159
1160         # Try (1)
1161         if source_version in ql:
1162             continue
1163
1164         # Try (2)
1165         from daklib.regexes import re_bin_only_nmu
1166         orig_source_version = re_bin_only_nmu.sub('', source_version)
1167         if orig_source_version in ql:
1168             continue
1169
1170         # No source found so return not ok
1171         return 0
1172
1173     # We're good
1174     return 1
1175
1176 __all__.append('source_exists')
1177
1178 def get_sources_from_name(source, dm_upload_allowed=None, session=None):
1179     """
1180     Returns list of DBSource objects for given C{source} name
1181
1182     @type source: str
1183     @param source: DBSource package name to search for
1184
1185     @type dm_upload_allowed: bool
1186     @param dm_upload_allowed: If None, no effect.  If True or False, only
1187     return packages with that dm_upload_allowed setting
1188
1189     @type session: Session
1190     @param session: Optional SQL session object (a temporary one will be
1191     generated if not supplied)
1192
1193     @rtype: list
1194     @return: list of DBSource objects for the given name (may be empty)
1195     """
1196     if session is None:
1197         session = DBConn().session()
1198
1199     q = session.query(DBSource).filter_by(source=source)
1200     if dm_upload_allowed is not None:
1201         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1202
1203     return q.all()
1204
1205 __all__.append('get_sources_from_name')
1206
1207 def get_source_in_suite(source, suite, session=None):
1208     """
1209     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1210
1211       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1212       - B{suite} - a suite name, eg. I{unstable}
1213
1214     @type source: string
1215     @param source: source package name
1216
1217     @type suite: string
1218     @param suite: the suite name
1219
1220     @rtype: string
1221     @return: the version for I{source} in I{suite}
1222
1223     """
1224     if session is None:
1225         session = DBConn().session()
1226     q = session.query(SrcAssociation)
1227     q = q.join('source').filter_by(source=source)
1228     q = q.join('suite').filter_by(suite_name=suite)
1229     if q.count() == 0:
1230         return None
1231     # ???: Maybe we should just return the SrcAssociation object instead
1232     return q.one().source
1233
1234 __all__.append('get_source_in_suite')
1235
1236 ################################################################################
1237
1238 class SrcAssociation(object):
1239     def __init__(self, *args, **kwargs):
1240         pass
1241
1242     def __repr__(self):
1243         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1244
1245 __all__.append('SrcAssociation')
1246
1247 ################################################################################
1248
1249 class SrcUploader(object):
1250     def __init__(self, *args, **kwargs):
1251         pass
1252
1253     def __repr__(self):
1254         return '<SrcUploader %s>' % self.uploader_id
1255
1256 __all__.append('SrcUploader')
1257
1258 ################################################################################
1259
1260 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1261                  ('SuiteID', 'suite_id'),
1262                  ('Version', 'version'),
1263                  ('Origin', 'origin'),
1264                  ('Label', 'label'),
1265                  ('Description', 'description'),
1266                  ('Untouchable', 'untouchable'),
1267                  ('Announce', 'announce'),
1268                  ('Codename', 'codename'),
1269                  ('OverrideCodename', 'overridecodename'),
1270                  ('ValidTime', 'validtime'),
1271                  ('Priority', 'priority'),
1272                  ('NotAutomatic', 'notautomatic'),
1273                  ('CopyChanges', 'copychanges'),
1274                  ('CopyDotDak', 'copydotdak'),
1275                  ('CommentsDir', 'commentsdir'),
1276                  ('OverrideSuite', 'overridesuite'),
1277                  ('ChangelogBase', 'changelogbase')]
1278
1279
1280 class Suite(object):
1281     def __init__(self, *args, **kwargs):
1282         pass
1283
1284     def __repr__(self):
1285         return '<Suite %s>' % self.suite_name
1286
1287     def details(self):
1288         ret = []
1289         for disp, field in SUITE_FIELDS:
1290             val = getattr(self, field, None)
1291             if val is not None:
1292                 ret.append("%s: %s" % (disp, val))
1293
1294         return "\n".join(ret)
1295
1296 __all__.append('Suite')
1297
1298 def get_suite_architecture(suite, architecture, session=None):
1299     """
1300     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1301     doesn't exist
1302
1303     @type suite: str
1304     @param suite: Suite name to search for
1305
1306     @type architecture: str
1307     @param architecture: Architecture name to search for
1308
1309     @type session: Session
1310     @param session: Optional SQL session object (a temporary one will be
1311     generated if not supplied)
1312
1313     @rtype: SuiteArchitecture
1314     @return: the SuiteArchitecture object or None
1315     """
1316
1317     if session is None:
1318         session = DBConn().session()
1319
1320     q = session.query(SuiteArchitecture)
1321     q = q.join(Architecture).filter_by(arch_string=architecture)
1322     q = q.join(Suite).filter_by(suite_name=suite)
1323     if q.count() == 0:
1324         return None
1325     return q.one()
1326
1327 __all__.append('get_suite_architecture')
1328
1329 def get_suite(suite, session=None):
1330     """
1331     Returns Suite object for given C{suite name}.
1332
1333     @type suite: string
1334     @param suite: The name of the suite
1335
1336     @type session: Session
1337     @param session: Optional SQLA session object (a temporary one will be
1338     generated if not supplied)
1339
1340     @rtype: Suite
1341     @return: Suite object for the requested suite name (None if not presenT)
1342
1343     """
1344     if session is None:
1345         session = DBConn().session()
1346     q = session.query(Suite).filter_by(suite_name=suite)
1347     if q.count() == 0:
1348         return None
1349     return q.one()
1350
1351 __all__.append('get_suite')
1352
1353 ################################################################################
1354
1355 class SuiteArchitecture(object):
1356     def __init__(self, *args, **kwargs):
1357         pass
1358
1359     def __repr__(self):
1360         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1361
1362 __all__.append('SuiteArchitecture')
1363
1364 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1365     """
1366     Returns list of Architecture objects for given C{suite} name
1367
1368     @type source: str
1369     @param source: Suite name to search for
1370
1371     @type skipsrc: boolean
1372     @param skipsrc: Whether to skip returning the 'source' architecture entry
1373     (Default False)
1374
1375     @type skipall: boolean
1376     @param skipall: Whether to skip returning the 'all' architecture entry
1377     (Default False)
1378
1379     @type session: Session
1380     @param session: Optional SQL session object (a temporary one will be
1381     generated if not supplied)
1382
1383     @rtype: list
1384     @return: list of Architecture objects for the given name (may be empty)
1385     """
1386
1387     if session is None:
1388         session = DBConn().session()
1389
1390     q = session.query(Architecture)
1391     q = q.join(SuiteArchitecture)
1392     q = q.join(Suite).filter_by(suite_name=suite)
1393     if skipsrc:
1394         q = q.filter(Architecture.arch_string != 'source')
1395     if skipall:
1396         q = q.filter(Architecture.arch_string != 'all')
1397     q = q.order_by('arch_string')
1398     return q.all()
1399
1400 __all__.append('get_suite_architectures')
1401
1402 ################################################################################
1403
1404 class Uid(object):
1405     def __init__(self, *args, **kwargs):
1406         pass
1407
1408     def __repr__(self):
1409         return '<Uid %s (%s)>' % (self.uid, self.name)
1410
1411 __all__.append('Uid')
1412
1413 def add_database_user(uidname, session=None):
1414     """
1415     Adds a database user
1416
1417     @type uidname: string
1418     @param uidname: The uid of the user to add
1419
1420     @type session: SQLAlchemy
1421     @param session: Optional SQL session object (a temporary one will be
1422     generated if not supplied).  If not passed, a commit will be performed at
1423     the end of the function, otherwise the caller is responsible for commiting.
1424
1425     @rtype: Uid
1426     @return: the uid object for the given uidname
1427     """
1428     privatetrans = False
1429     if session is None:
1430         session = DBConn().session()
1431         privatetrans = True
1432
1433     try:
1434         session.execute("CREATE USER :uid", {'uid': uidname})
1435         if privatetrans:
1436             session.commit()
1437     except:
1438         traceback.print_exc()
1439         raise
1440
1441 __all__.append('add_database_user')
1442
1443 def get_or_set_uid(uidname, session=None):
1444     """
1445     Returns uid object for given uidname.
1446
1447     If no matching uidname is found, a row is inserted.
1448
1449     @type uidname: string
1450     @param uidname: The uid to add
1451
1452     @type session: SQLAlchemy
1453     @param session: Optional SQL session object (a temporary one will be
1454     generated if not supplied).  If not passed, a commit will be performed at
1455     the end of the function, otherwise the caller is responsible for commiting.
1456
1457     @rtype: Uid
1458     @return: the uid object for the given uidname
1459     """
1460     privatetrans = False
1461     if session is None:
1462         session = DBConn().session()
1463         privatetrans = True
1464
1465     try:
1466         q = session.query(Uid).filter_by(uid=uidname)
1467         if q.count() < 1:
1468             uid = Uid()
1469             uid.uid = uidname
1470             session.add(uid)
1471             if privatetrans:
1472                 session.commit()
1473             return uid
1474         else:
1475             return q.one()
1476
1477     except:
1478         traceback.print_exc()
1479         raise
1480
1481 __all__.append('get_or_set_uid')
1482
1483
1484 def get_uid_from_fingerprint(fpr, session=None):
1485     if session is None:
1486         session = DBConn().session()
1487
1488     q = session.query(Uid)
1489     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
1490
1491     if q.count() != 1:
1492         return None
1493     else:
1494         return q.one()
1495
1496 __all__.append('get_uid_from_fingerprint')
1497
1498 ################################################################################
1499
1500 class DBConn(Singleton):
1501     """
1502     database module init.
1503     """
1504     def __init__(self, *args, **kwargs):
1505         super(DBConn, self).__init__(*args, **kwargs)
1506
1507     def _startup(self, *args, **kwargs):
1508         self.debug = False
1509         if kwargs.has_key('debug'):
1510             self.debug = True
1511         self.__createconn()
1512
1513     def __setuptables(self):
1514         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
1515         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
1516         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
1517         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
1518         self.tbl_component = Table('component', self.db_meta, autoload=True)
1519         self.tbl_config = Table('config', self.db_meta, autoload=True)
1520         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
1521         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
1522         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
1523         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
1524         self.tbl_files = Table('files', self.db_meta, autoload=True)
1525         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
1526         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
1527         self.tbl_location = Table('location', self.db_meta, autoload=True)
1528         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
1529         self.tbl_override = Table('override', self.db_meta, autoload=True)
1530         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
1531         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
1532         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
1533         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
1534         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
1535         self.tbl_section = Table('section', self.db_meta, autoload=True)
1536         self.tbl_source = Table('source', self.db_meta, autoload=True)
1537         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
1538         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
1539         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
1540         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
1541         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
1542
1543     def __setupmappers(self):
1544         mapper(Architecture, self.tbl_architecture,
1545                properties = dict(arch_id = self.tbl_architecture.c.id))
1546
1547         mapper(Archive, self.tbl_archive,
1548                properties = dict(archive_id = self.tbl_archive.c.id,
1549                                  archive_name = self.tbl_archive.c.name))
1550
1551         mapper(BinAssociation, self.tbl_bin_associations,
1552                properties = dict(ba_id = self.tbl_bin_associations.c.id,
1553                                  suite_id = self.tbl_bin_associations.c.suite,
1554                                  suite = relation(Suite),
1555                                  binary_id = self.tbl_bin_associations.c.bin,
1556                                  binary = relation(DBBinary)))
1557
1558         mapper(DBBinary, self.tbl_binaries,
1559                properties = dict(binary_id = self.tbl_binaries.c.id,
1560                                  package = self.tbl_binaries.c.package,
1561                                  version = self.tbl_binaries.c.version,
1562                                  maintainer_id = self.tbl_binaries.c.maintainer,
1563                                  maintainer = relation(Maintainer),
1564                                  source_id = self.tbl_binaries.c.source,
1565                                  source = relation(DBSource),
1566                                  arch_id = self.tbl_binaries.c.architecture,
1567                                  architecture = relation(Architecture),
1568                                  poolfile_id = self.tbl_binaries.c.file,
1569                                  poolfile = relation(PoolFile),
1570                                  binarytype = self.tbl_binaries.c.type,
1571                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
1572                                  fingerprint = relation(Fingerprint),
1573                                  install_date = self.tbl_binaries.c.install_date,
1574                                  binassociations = relation(BinAssociation,
1575                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
1576
1577         mapper(Component, self.tbl_component,
1578                properties = dict(component_id = self.tbl_component.c.id,
1579                                  component_name = self.tbl_component.c.name))
1580
1581         mapper(DBConfig, self.tbl_config,
1582                properties = dict(config_id = self.tbl_config.c.id))
1583
1584         mapper(ContentAssociation, self.tbl_content_associations,
1585                properties = dict(ca_id = self.tbl_content_associations.c.id,
1586                                  filename_id = self.tbl_content_associations.c.filename,
1587                                  filename    = relation(ContentFilename),
1588                                  filepath_id = self.tbl_content_associations.c.filepath,
1589                                  filepath    = relation(ContentFilepath),
1590                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
1591                                  binary      = relation(DBBinary)))
1592
1593
1594         mapper(ContentFilename, self.tbl_content_file_names,
1595                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
1596                                  filename = self.tbl_content_file_names.c.file))
1597
1598         mapper(ContentFilepath, self.tbl_content_file_paths,
1599                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
1600                                  filepath = self.tbl_content_file_paths.c.path))
1601
1602         mapper(DSCFile, self.tbl_dsc_files,
1603                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
1604                                  source_id = self.tbl_dsc_files.c.source,
1605                                  source = relation(DBSource),
1606                                  poolfile_id = self.tbl_dsc_files.c.file,
1607                                  poolfile = relation(PoolFile)))
1608
1609         mapper(PoolFile, self.tbl_files,
1610                properties = dict(file_id = self.tbl_files.c.id,
1611                                  filesize = self.tbl_files.c.size,
1612                                  location_id = self.tbl_files.c.location,
1613                                  location = relation(Location)))
1614
1615         mapper(Fingerprint, self.tbl_fingerprint,
1616                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
1617                                  uid_id = self.tbl_fingerprint.c.uid,
1618                                  uid = relation(Uid),
1619                                  keyring_id = self.tbl_fingerprint.c.keyring,
1620                                  keyring = relation(Keyring)))
1621
1622         mapper(Keyring, self.tbl_keyrings,
1623                properties = dict(keyring_name = self.tbl_keyrings.c.name,
1624                                  keyring_id = self.tbl_keyrings.c.id))
1625
1626         mapper(Location, self.tbl_location,
1627                properties = dict(location_id = self.tbl_location.c.id,
1628                                  component_id = self.tbl_location.c.component,
1629                                  component = relation(Component),
1630                                  archive_id = self.tbl_location.c.archive,
1631                                  archive = relation(Archive),
1632                                  archive_type = self.tbl_location.c.type))
1633
1634         mapper(Maintainer, self.tbl_maintainer,
1635                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
1636
1637         mapper(Override, self.tbl_override,
1638                properties = dict(suite_id = self.tbl_override.c.suite,
1639                                  suite = relation(Suite),
1640                                  component_id = self.tbl_override.c.component,
1641                                  component = relation(Component),
1642                                  priority_id = self.tbl_override.c.priority,
1643                                  priority = relation(Priority),
1644                                  section_id = self.tbl_override.c.section,
1645                                  section = relation(Section),
1646                                  overridetype_id = self.tbl_override.c.type,
1647                                  overridetype = relation(OverrideType)))
1648
1649         mapper(OverrideType, self.tbl_override_type,
1650                properties = dict(overridetype = self.tbl_override_type.c.type,
1651                                  overridetype_id = self.tbl_override_type.c.id))
1652
1653         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
1654                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
1655                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
1656                                  filepath = relation(ContentFilepath),
1657                                  filename_id = self.tbl_pending_content_associations.c.filename,
1658                                  filename = relation(ContentFilename)))
1659
1660         mapper(Priority, self.tbl_priority,
1661                properties = dict(priority_id = self.tbl_priority.c.id))
1662
1663         mapper(Queue, self.tbl_queue,
1664                properties = dict(queue_id = self.tbl_queue.c.id))
1665
1666         mapper(QueueBuild, self.tbl_queue_build,
1667                properties = dict(suite_id = self.tbl_queue_build.c.suite,
1668                                  queue_id = self.tbl_queue_build.c.queue,
1669                                  queue = relation(Queue, backref='queuebuild')))
1670
1671         mapper(Section, self.tbl_section,
1672                properties = dict(section_id = self.tbl_section.c.id))
1673
1674         mapper(DBSource, self.tbl_source,
1675                properties = dict(source_id = self.tbl_source.c.id,
1676                                  version = self.tbl_source.c.version,
1677                                  maintainer_id = self.tbl_source.c.maintainer,
1678                                  maintainer = relation(Maintainer,
1679                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
1680                                  poolfile_id = self.tbl_source.c.file,
1681                                  poolfile = relation(PoolFile),
1682                                  fingerprint_id = self.tbl_source.c.sig_fpr,
1683                                  fingerprint = relation(Fingerprint),
1684                                  changedby_id = self.tbl_source.c.changedby,
1685                                  changedby = relation(Maintainer,
1686                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
1687                                  srcfiles = relation(DSCFile,
1688                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
1689                                  srcassociations = relation(SrcAssociation,
1690                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
1691
1692         mapper(SrcAssociation, self.tbl_src_associations,
1693                properties = dict(sa_id = self.tbl_src_associations.c.id,
1694                                  suite_id = self.tbl_src_associations.c.suite,
1695                                  suite = relation(Suite),
1696                                  source_id = self.tbl_src_associations.c.source,
1697                                  source = relation(DBSource)))
1698
1699         mapper(SrcUploader, self.tbl_src_uploaders,
1700                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
1701                                  source_id = self.tbl_src_uploaders.c.source,
1702                                  source = relation(DBSource,
1703                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
1704                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
1705                                  maintainer = relation(Maintainer,
1706                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
1707
1708         mapper(Suite, self.tbl_suite,
1709                properties = dict(suite_id = self.tbl_suite.c.id))
1710
1711         mapper(SuiteArchitecture, self.tbl_suite_architectures,
1712                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
1713                                  suite = relation(Suite, backref='suitearchitectures'),
1714                                  arch_id = self.tbl_suite_architectures.c.architecture,
1715                                  architecture = relation(Architecture)))
1716
1717         mapper(Uid, self.tbl_uid,
1718                properties = dict(uid_id = self.tbl_uid.c.id,
1719                                  fingerprint = relation(Fingerprint)))
1720
1721     ## Connection functions
1722     def __createconn(self):
1723         from config import Config
1724         cnf = Config()
1725         if cnf["DB::Host"]:
1726             # TCP/IP
1727             connstr = "postgres://%s" % cnf["DB::Host"]
1728             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1729                 connstr += ":%s" % cnf["DB::Port"]
1730             connstr += "/%s" % cnf["DB::Name"]
1731         else:
1732             # Unix Socket
1733             connstr = "postgres:///%s" % cnf["DB::Name"]
1734             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1735                 connstr += "?port=%s" % cnf["DB::Port"]
1736
1737         self.db_pg   = create_engine(connstr, echo=self.debug)
1738         self.db_meta = MetaData()
1739         self.db_meta.bind = self.db_pg
1740         self.db_smaker = sessionmaker(bind=self.db_pg,
1741                                       autoflush=True,
1742                                       autocommit=False)
1743
1744         self.__setuptables()
1745         self.__setupmappers()
1746
1747     def session(self):
1748         return self.db_smaker()
1749
1750 __all__.append('DBConn')
1751
1752