]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
add new comments support to dbconn
[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 NewComment(object):
673     def __init__(self, *args, **kwargs):
674         pass
675
676     def __repr__(self):
677         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
678
679 __all__.append('NewComment')
680
681 def has_new_comment(package, version, session=None):
682     """
683     Returns true if the given combination of C{package}, C{version} has a comment.
684
685     @type package: string
686     @param package: name of the package
687
688     @type version: string
689     @param version: package version
690
691     @type session: Session
692     @param session: Optional SQLA session object (a temporary one will be
693     generated if not supplied)
694
695     @rtype: boolean
696     @return: true/false
697     """
698
699     if session is None:
700         session = DBConn().session()
701
702     q = session.query(NewComment)
703     q = q.filter_by(package=package)
704     q = q.filter_by(version=version)
705     return q.count() > 0
706
707 __all__.append('has_new_comment')
708
709 def get_new_comments(package=None, version=None, comment_id=None, session=None):
710     """
711     Returns (possibly empty) list of NewComment objects for the given
712     parameters
713
714     @type package: string (optional)
715     @param package: name of the package
716
717     @type version: string (optional)
718     @param version: package version
719
720     @type comment_id: int (optional)
721     @param comment_id: An id of a comment
722
723     @type session: Session
724     @param session: Optional SQLA session object (a temporary one will be
725     generated if not supplied)
726
727     @rtype: list
728     @return: A (possibly empty) list of NewComment objects will be returned
729
730     """
731
732     if session is None:
733         session = DBConn().session()
734
735     q = session.query(NewComment)
736     if package is not None: q = q.filter_by(package=package)
737     if version is not None: q = q.filter_by(version=version)
738     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
739
740     return q.all()
741
742 __all__.append('get_new_comments')
743
744 ################################################################################
745
746 class Override(object):
747     def __init__(self, *args, **kwargs):
748         pass
749
750     def __repr__(self):
751         return '<Override %s (%s)>' % (self.package, self.suite_id)
752
753 __all__.append('Override')
754
755 def get_override(package, suite=None, component=None, overridetype=None, session=None):
756     """
757     Returns Override object for the given parameters
758
759     @type package: string
760     @param package: The name of the package
761
762     @type suite: string, list or None
763     @param suite: The name of the suite (or suites if a list) to limit to.  If
764                   None, don't limit.  Defaults to None.
765
766     @type component: string, list or None
767     @param component: The name of the component (or components if a list) to
768                       limit to.  If None, don't limit.  Defaults to None.
769
770     @type overridetype: string, list or None
771     @param overridetype: The name of the overridetype (or overridetypes if a list) to
772                          limit to.  If None, don't limit.  Defaults to None.
773
774     @type session: Session
775     @param session: Optional SQLA session object (a temporary one will be
776     generated if not supplied)
777
778     @rtype: list
779     @return: A (possibly empty) list of Override objects will be returned
780
781     """
782     if session is None:
783         session = DBConn().session()
784
785     q = session.query(Override)
786     q = q.filter_by(package=package)
787
788     if suite is not None:
789         if not isinstance(suite, list): suite = [suite]
790         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
791
792     if component is not None:
793         if not isinstance(component, list): component = [component]
794         q = q.join(Component).filter(Component.component_name.in_(component))
795
796     if overridetype is not None:
797         if not isinstance(overridetype, list): overridetype = [overridetype]
798         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
799
800     return q.all()
801
802 __all__.append('get_override')
803
804
805 ################################################################################
806
807 class OverrideType(object):
808     def __init__(self, *args, **kwargs):
809         pass
810
811     def __repr__(self):
812         return '<OverrideType %s>' % self.overridetype
813
814 __all__.append('OverrideType')
815
816 def get_override_type(override_type, session=None):
817     """
818     Returns OverrideType object for given C{override type}.
819
820     @type override_type: string
821     @param override_type: The name of the override type
822
823     @type session: Session
824     @param session: Optional SQLA session object (a temporary one will be
825     generated if not supplied)
826
827     @rtype: int
828     @return: the database id for the given override type
829
830     """
831     if session is None:
832         session = DBConn().session()
833     q = session.query(OverrideType).filter_by(overridetype=override_type)
834     if q.count() == 0:
835         return None
836     return q.one()
837
838 __all__.append('get_override_type')
839
840 ################################################################################
841
842 class PendingContentAssociation(object):
843     def __init__(self, *args, **kwargs):
844         pass
845
846     def __repr__(self):
847         return '<PendingContentAssociation %s>' % self.pca_id
848
849 __all__.append('PendingContentAssociation')
850
851 def insert_pending_content_paths(package, fullpaths, session=None):
852     """
853     Make sure given paths are temporarily associated with given
854     package
855
856     @type package: dict
857     @param package: the package to associate with should have been read in from the binary control file
858     @type fullpaths: list
859     @param fullpaths: the list of paths of the file being associated with the binary
860     @type session: SQLAlchemy session
861     @param session: Optional SQLAlchemy session.  If this is passed, the caller
862     is responsible for ensuring a transaction has begun and committing the
863     results or rolling back based on the result code.  If not passed, a commit
864     will be performed at the end of the function
865
866     @return: True upon success, False if there is a problem
867     """
868
869     privatetrans = False
870
871     if session is None:
872         session = DBConn().session()
873         privatetrans = True
874
875     try:
876         arch = get_architecture(package['Architecture'], session)
877         arch_id = arch.arch_id
878
879         # Remove any already existing recorded files for this package
880         q = session.query(PendingContentAssociation)
881         q = q.filter_by(package=package['Package'])
882         q = q.filter_by(version=package['Version'])
883         q = q.filter_by(architecture=arch_id)
884         q.delete()
885
886         # Insert paths
887         for fullpath in fullpaths:
888             (path, file) = os.path.split(fullpath)
889
890             if path.startswith( "./" ):
891                 path = path[2:]
892
893             pca = PendingContentAssociation()
894             pca.package = package['Package']
895             pca.version = package['Version']
896             pca.filename_id = get_or_set_contents_file_id(file, session)
897             pca.filepath_id = get_or_set_contents_path_id(path, session)
898             pca.architecture = arch_id
899             session.add(pca)
900
901         # Only commit if we set up the session ourself
902         if privatetrans:
903             session.commit()
904
905         return True
906     except:
907         traceback.print_exc()
908
909         # Only rollback if we set up the session ourself
910         if privatetrans:
911             session.rollback()
912
913         return False
914
915 __all__.append('insert_pending_content_paths')
916
917 ################################################################################
918
919 class Priority(object):
920     def __init__(self, *args, **kwargs):
921         pass
922
923     def __repr__(self):
924         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
925
926 __all__.append('Priority')
927
928 def get_priority(priority, session=None):
929     """
930     Returns Priority object for given C{priority name}.
931
932     @type priority: string
933     @param priority: The name of the priority
934
935     @type session: Session
936     @param session: Optional SQLA session object (a temporary one will be
937     generated if not supplied)
938
939     @rtype: Priority
940     @return: Priority object for the given priority
941
942     """
943     if session is None:
944         session = DBConn().session()
945     q = session.query(Priority).filter_by(priority=priority)
946     if q.count() == 0:
947         return None
948     return q.one()
949
950 __all__.append('get_priority')
951
952 ################################################################################
953
954 class Queue(object):
955     def __init__(self, *args, **kwargs):
956         pass
957
958     def __repr__(self):
959         return '<Queue %s>' % self.queue_name
960
961     def autobuild_upload(self, changes, srcpath, session=None):
962         """
963         Update queue_build database table used for incoming autobuild support.
964
965         @type changes: Changes
966         @param changes: changes object for the upload to process
967
968         @type srcpath: string
969         @param srcpath: path for the queue file entries/link destinations
970
971         @type session: SQLAlchemy session
972         @param session: Optional SQLAlchemy session.  If this is passed, the
973         caller is responsible for ensuring a transaction has begun and
974         committing the results or rolling back based on the result code.  If
975         not passed, a commit will be performed at the end of the function,
976         otherwise the caller is responsible for commiting.
977
978         @rtype: NoneType or string
979         @return: None if the operation failed, a string describing the error if not
980         """
981
982         localcommit = False
983         if session is None:
984             session = DBConn().session()
985             localcommit = True
986
987         # TODO: Remove by moving queue config into the database
988         conf = Config()
989
990         for suitename in changes.changes["distribution"].keys():
991             # TODO: Move into database as:
992             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
993             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
994             #       This also gets rid of the SecurityQueueBuild hack below
995             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
996                 continue
997
998             # Find suite object
999             s = get_suite(suitename, session)
1000             if s is None:
1001                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1002
1003             # TODO: Get from database as above
1004             dest_dir = conf["Dir::QueueBuild"]
1005
1006             # TODO: Move into database as above
1007             if conf.FindB("Dinstall::SecurityQueueBuild"):
1008                 dest_dir = os.path.join(dest_dir, suitename)
1009
1010             for file_entry in changes.files.keys():
1011                 src = os.path.join(srcpath, file_entry)
1012                 dest = os.path.join(dest_dir, file_entry)
1013
1014                 # TODO: Move into database as above
1015                 if Cnf.FindB("Dinstall::SecurityQueueBuild"):
1016                     # Copy it since the original won't be readable by www-data
1017                     utils.copy(src, dest)
1018                 else:
1019                     # Create a symlink to it
1020                     os.symlink(src, dest)
1021
1022                 qb = QueueBuild()
1023                 qb.suite_id = s.suite_id
1024                 qb.queue_id = self.queue_id
1025                 qb.filename = dest
1026                 qb.in_queue = True
1027
1028                 session.add(qb)
1029
1030             # If the .orig.tar.gz is in the pool, create a symlink to
1031             # it (if one doesn't already exist)
1032             if changes.orig_tar_id:
1033                 # Determine the .orig.tar.gz file name
1034                 for dsc_file in changes.dsc_files.keys():
1035                     if dsc_file.endswith(".orig.tar.gz"):
1036                         filename = dsc_file
1037
1038                 dest = os.path.join(dest_dir, filename)
1039
1040                 # If it doesn't exist, create a symlink
1041                 if not os.path.exists(dest):
1042                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1043                                         {'id': changes.orig_tar_id})
1044                     res = q.fetchone()
1045                     if not res:
1046                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1047
1048                     src = os.path.join(res[0], res[1])
1049                     os.symlink(src, dest)
1050
1051                     # Add it to the list of packages for later processing by apt-ftparchive
1052                     qb = QueueBuild()
1053                     qb.suite_id = s.suite_id
1054                     qb.queue_id = self.queue_id
1055                     qb.filename = dest
1056                     qb.in_queue = True
1057                     session.add(qb)
1058
1059                 # If it does, update things to ensure it's not removed prematurely
1060                 else:
1061                     qb = get_queue_build(dest, suite_id, session)
1062                     if qb is None:
1063                         qb.in_queue = True
1064                         qb.last_used = None
1065                         session.add(qb)
1066
1067         if localcommit:
1068             session.commit()
1069
1070         return None
1071
1072 __all__.append('Queue')
1073
1074 def get_queue(queuename, session=None):
1075     """
1076     Returns Queue object for given C{queue name}.
1077
1078     @type queuename: string
1079     @param queuename: The name of the queue
1080
1081     @type session: Session
1082     @param session: Optional SQLA session object (a temporary one will be
1083     generated if not supplied)
1084
1085     @rtype: Queue
1086     @return: Queue object for the given queue
1087
1088     """
1089     if session is None:
1090         session = DBConn().session()
1091     q = session.query(Queue).filter_by(queue_name=queuename)
1092     if q.count() == 0:
1093         return None
1094     return q.one()
1095
1096 __all__.append('get_queue')
1097
1098 ################################################################################
1099
1100 class QueueBuild(object):
1101     def __init__(self, *args, **kwargs):
1102         pass
1103
1104     def __repr__(self):
1105         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1106
1107 __all__.append('QueueBuild')
1108
1109 def get_queue_build(filename, suite_id, session=None):
1110     """
1111     Returns QueueBuild object for given C{filename} and C{suite id}.
1112
1113     @type filename: string
1114     @param filename: The name of the file
1115
1116     @type suiteid: int
1117     @param suiteid: Suite ID
1118
1119     @type session: Session
1120     @param session: Optional SQLA session object (a temporary one will be
1121     generated if not supplied)
1122
1123     @rtype: Queue
1124     @return: Queue object for the given queue
1125
1126     """
1127     if session is None:
1128         session = DBConn().session()
1129     q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite_id)
1130     if q.count() == 0:
1131         return None
1132     return q.one()
1133
1134 __all__.append('get_queue_build')
1135
1136 ################################################################################
1137
1138 class Section(object):
1139     def __init__(self, *args, **kwargs):
1140         pass
1141
1142     def __repr__(self):
1143         return '<Section %s>' % self.section
1144
1145 __all__.append('Section')
1146
1147 def get_section(section, session=None):
1148     """
1149     Returns Section object for given C{section name}.
1150
1151     @type section: string
1152     @param section: The name of the section
1153
1154     @type session: Session
1155     @param session: Optional SQLA session object (a temporary one will be
1156     generated if not supplied)
1157
1158     @rtype: Section
1159     @return: Section object for the given section name
1160
1161     """
1162     if session is None:
1163         session = DBConn().session()
1164     q = session.query(Section).filter_by(section=section)
1165     if q.count() == 0:
1166         return None
1167     return q.one()
1168
1169 __all__.append('get_section')
1170
1171 ################################################################################
1172
1173 class DBSource(object):
1174     def __init__(self, *args, **kwargs):
1175         pass
1176
1177     def __repr__(self):
1178         return '<DBSource %s (%s)>' % (self.source, self.version)
1179
1180 __all__.append('DBSource')
1181
1182 def source_exists(source, source_version, suites = ["any"], session=None):
1183     """
1184     Ensure that source exists somewhere in the archive for the binary
1185     upload being processed.
1186       1. exact match     => 1.0-3
1187       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1188
1189     @type package: string
1190     @param package: package source name
1191
1192     @type source_version: string
1193     @param source_version: expected source version
1194
1195     @type suites: list
1196     @param suites: list of suites to check in, default I{any}
1197
1198     @type session: Session
1199     @param session: Optional SQLA session object (a temporary one will be
1200     generated if not supplied)
1201
1202     @rtype: int
1203     @return: returns 1 if a source with expected version is found, otherwise 0
1204
1205     """
1206
1207     if session is None:
1208         session = DBConn().session()
1209
1210     cnf = Config()
1211
1212     for suite in suites:
1213         q = session.query(DBSource).filter_by(source=source)
1214         if suite != "any":
1215             # source must exist in suite X, or in some other suite that's
1216             # mapped to X, recursively... silent-maps are counted too,
1217             # unreleased-maps aren't.
1218             maps = cnf.ValueList("SuiteMappings")[:]
1219             maps.reverse()
1220             maps = [ m.split() for m in maps ]
1221             maps = [ (x[1], x[2]) for x in maps
1222                             if x[0] == "map" or x[0] == "silent-map" ]
1223             s = [suite]
1224             for x in maps:
1225                 if x[1] in s and x[0] not in s:
1226                     s.append(x[0])
1227
1228             q = q.join(SrcAssociation).join(Suite)
1229             q = q.filter(Suite.suite_name.in_(s))
1230
1231         # Reduce the query results to a list of version numbers
1232         ql = [ j.version for j in q.all() ]
1233
1234         # Try (1)
1235         if source_version in ql:
1236             continue
1237
1238         # Try (2)
1239         from daklib.regexes import re_bin_only_nmu
1240         orig_source_version = re_bin_only_nmu.sub('', source_version)
1241         if orig_source_version in ql:
1242             continue
1243
1244         # No source found so return not ok
1245         return 0
1246
1247     # We're good
1248     return 1
1249
1250 __all__.append('source_exists')
1251
1252 def get_sources_from_name(source, dm_upload_allowed=None, session=None):
1253     """
1254     Returns list of DBSource objects for given C{source} name
1255
1256     @type source: str
1257     @param source: DBSource package name to search for
1258
1259     @type dm_upload_allowed: bool
1260     @param dm_upload_allowed: If None, no effect.  If True or False, only
1261     return packages with that dm_upload_allowed setting
1262
1263     @type session: Session
1264     @param session: Optional SQL session object (a temporary one will be
1265     generated if not supplied)
1266
1267     @rtype: list
1268     @return: list of DBSource objects for the given name (may be empty)
1269     """
1270     if session is None:
1271         session = DBConn().session()
1272
1273     q = session.query(DBSource).filter_by(source=source)
1274     if dm_upload_allowed is not None:
1275         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1276
1277     return q.all()
1278
1279 __all__.append('get_sources_from_name')
1280
1281 def get_source_in_suite(source, suite, session=None):
1282     """
1283     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1284
1285       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1286       - B{suite} - a suite name, eg. I{unstable}
1287
1288     @type source: string
1289     @param source: source package name
1290
1291     @type suite: string
1292     @param suite: the suite name
1293
1294     @rtype: string
1295     @return: the version for I{source} in I{suite}
1296
1297     """
1298     if session is None:
1299         session = DBConn().session()
1300     q = session.query(SrcAssociation)
1301     q = q.join('source').filter_by(source=source)
1302     q = q.join('suite').filter_by(suite_name=suite)
1303     if q.count() == 0:
1304         return None
1305     # ???: Maybe we should just return the SrcAssociation object instead
1306     return q.one().source
1307
1308 __all__.append('get_source_in_suite')
1309
1310 ################################################################################
1311
1312 class SrcAssociation(object):
1313     def __init__(self, *args, **kwargs):
1314         pass
1315
1316     def __repr__(self):
1317         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1318
1319 __all__.append('SrcAssociation')
1320
1321 ################################################################################
1322
1323 class SrcUploader(object):
1324     def __init__(self, *args, **kwargs):
1325         pass
1326
1327     def __repr__(self):
1328         return '<SrcUploader %s>' % self.uploader_id
1329
1330 __all__.append('SrcUploader')
1331
1332 ################################################################################
1333
1334 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1335                  ('SuiteID', 'suite_id'),
1336                  ('Version', 'version'),
1337                  ('Origin', 'origin'),
1338                  ('Label', 'label'),
1339                  ('Description', 'description'),
1340                  ('Untouchable', 'untouchable'),
1341                  ('Announce', 'announce'),
1342                  ('Codename', 'codename'),
1343                  ('OverrideCodename', 'overridecodename'),
1344                  ('ValidTime', 'validtime'),
1345                  ('Priority', 'priority'),
1346                  ('NotAutomatic', 'notautomatic'),
1347                  ('CopyChanges', 'copychanges'),
1348                  ('CopyDotDak', 'copydotdak'),
1349                  ('CommentsDir', 'commentsdir'),
1350                  ('OverrideSuite', 'overridesuite'),
1351                  ('ChangelogBase', 'changelogbase')]
1352
1353
1354 class Suite(object):
1355     def __init__(self, *args, **kwargs):
1356         pass
1357
1358     def __repr__(self):
1359         return '<Suite %s>' % self.suite_name
1360
1361     def details(self):
1362         ret = []
1363         for disp, field in SUITE_FIELDS:
1364             val = getattr(self, field, None)
1365             if val is not None:
1366                 ret.append("%s: %s" % (disp, val))
1367
1368         return "\n".join(ret)
1369
1370 __all__.append('Suite')
1371
1372 def get_suite_architecture(suite, architecture, session=None):
1373     """
1374     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1375     doesn't exist
1376
1377     @type suite: str
1378     @param suite: Suite name to search for
1379
1380     @type architecture: str
1381     @param architecture: Architecture name to search for
1382
1383     @type session: Session
1384     @param session: Optional SQL session object (a temporary one will be
1385     generated if not supplied)
1386
1387     @rtype: SuiteArchitecture
1388     @return: the SuiteArchitecture object or None
1389     """
1390
1391     if session is None:
1392         session = DBConn().session()
1393
1394     q = session.query(SuiteArchitecture)
1395     q = q.join(Architecture).filter_by(arch_string=architecture)
1396     q = q.join(Suite).filter_by(suite_name=suite)
1397     if q.count() == 0:
1398         return None
1399     return q.one()
1400
1401 __all__.append('get_suite_architecture')
1402
1403 def get_suite(suite, session=None):
1404     """
1405     Returns Suite object for given C{suite name}.
1406
1407     @type suite: string
1408     @param suite: The name of the suite
1409
1410     @type session: Session
1411     @param session: Optional SQLA session object (a temporary one will be
1412     generated if not supplied)
1413
1414     @rtype: Suite
1415     @return: Suite object for the requested suite name (None if not presenT)
1416
1417     """
1418     if session is None:
1419         session = DBConn().session()
1420     q = session.query(Suite).filter_by(suite_name=suite)
1421     if q.count() == 0:
1422         return None
1423     return q.one()
1424
1425 __all__.append('get_suite')
1426
1427 ################################################################################
1428
1429 class SuiteArchitecture(object):
1430     def __init__(self, *args, **kwargs):
1431         pass
1432
1433     def __repr__(self):
1434         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1435
1436 __all__.append('SuiteArchitecture')
1437
1438 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1439     """
1440     Returns list of Architecture objects for given C{suite} name
1441
1442     @type source: str
1443     @param source: Suite name to search for
1444
1445     @type skipsrc: boolean
1446     @param skipsrc: Whether to skip returning the 'source' architecture entry
1447     (Default False)
1448
1449     @type skipall: boolean
1450     @param skipall: Whether to skip returning the 'all' architecture entry
1451     (Default False)
1452
1453     @type session: Session
1454     @param session: Optional SQL session object (a temporary one will be
1455     generated if not supplied)
1456
1457     @rtype: list
1458     @return: list of Architecture objects for the given name (may be empty)
1459     """
1460
1461     if session is None:
1462         session = DBConn().session()
1463
1464     q = session.query(Architecture)
1465     q = q.join(SuiteArchitecture)
1466     q = q.join(Suite).filter_by(suite_name=suite)
1467     if skipsrc:
1468         q = q.filter(Architecture.arch_string != 'source')
1469     if skipall:
1470         q = q.filter(Architecture.arch_string != 'all')
1471     q = q.order_by('arch_string')
1472     return q.all()
1473
1474 __all__.append('get_suite_architectures')
1475
1476 ################################################################################
1477
1478 class Uid(object):
1479     def __init__(self, *args, **kwargs):
1480         pass
1481
1482     def __repr__(self):
1483         return '<Uid %s (%s)>' % (self.uid, self.name)
1484
1485 __all__.append('Uid')
1486
1487 def add_database_user(uidname, session=None):
1488     """
1489     Adds a database user
1490
1491     @type uidname: string
1492     @param uidname: The uid of the user to add
1493
1494     @type session: SQLAlchemy
1495     @param session: Optional SQL session object (a temporary one will be
1496     generated if not supplied).  If not passed, a commit will be performed at
1497     the end of the function, otherwise the caller is responsible for commiting.
1498
1499     @rtype: Uid
1500     @return: the uid object for the given uidname
1501     """
1502     privatetrans = False
1503     if session is None:
1504         session = DBConn().session()
1505         privatetrans = True
1506
1507     try:
1508         session.execute("CREATE USER :uid", {'uid': uidname})
1509         if privatetrans:
1510             session.commit()
1511     except:
1512         traceback.print_exc()
1513         raise
1514
1515 __all__.append('add_database_user')
1516
1517 def get_or_set_uid(uidname, session=None):
1518     """
1519     Returns uid object for given uidname.
1520
1521     If no matching uidname is found, a row is inserted.
1522
1523     @type uidname: string
1524     @param uidname: The uid to add
1525
1526     @type session: SQLAlchemy
1527     @param session: Optional SQL session object (a temporary one will be
1528     generated if not supplied).  If not passed, a commit will be performed at
1529     the end of the function, otherwise the caller is responsible for commiting.
1530
1531     @rtype: Uid
1532     @return: the uid object for the given uidname
1533     """
1534     privatetrans = False
1535     if session is None:
1536         session = DBConn().session()
1537         privatetrans = True
1538
1539     try:
1540         q = session.query(Uid).filter_by(uid=uidname)
1541         if q.count() < 1:
1542             uid = Uid()
1543             uid.uid = uidname
1544             session.add(uid)
1545             if privatetrans:
1546                 session.commit()
1547             return uid
1548         else:
1549             return q.one()
1550
1551     except:
1552         traceback.print_exc()
1553         raise
1554
1555 __all__.append('get_or_set_uid')
1556
1557
1558 def get_uid_from_fingerprint(fpr, session=None):
1559     if session is None:
1560         session = DBConn().session()
1561
1562     q = session.query(Uid)
1563     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
1564
1565     if q.count() != 1:
1566         return None
1567     else:
1568         return q.one()
1569
1570 __all__.append('get_uid_from_fingerprint')
1571
1572 ################################################################################
1573
1574 class DBConn(Singleton):
1575     """
1576     database module init.
1577     """
1578     def __init__(self, *args, **kwargs):
1579         super(DBConn, self).__init__(*args, **kwargs)
1580
1581     def _startup(self, *args, **kwargs):
1582         self.debug = False
1583         if kwargs.has_key('debug'):
1584             self.debug = True
1585         self.__createconn()
1586
1587     def __setuptables(self):
1588         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
1589         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
1590         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
1591         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
1592         self.tbl_component = Table('component', self.db_meta, autoload=True)
1593         self.tbl_config = Table('config', self.db_meta, autoload=True)
1594         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
1595         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
1596         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
1597         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
1598         self.tbl_files = Table('files', self.db_meta, autoload=True)
1599         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
1600         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
1601         self.tbl_location = Table('location', self.db_meta, autoload=True)
1602         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
1603         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
1604         self.tbl_override = Table('override', self.db_meta, autoload=True)
1605         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
1606         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
1607         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
1608         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
1609         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
1610         self.tbl_section = Table('section', self.db_meta, autoload=True)
1611         self.tbl_source = Table('source', self.db_meta, autoload=True)
1612         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
1613         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
1614         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
1615         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
1616         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
1617
1618     def __setupmappers(self):
1619         mapper(Architecture, self.tbl_architecture,
1620                properties = dict(arch_id = self.tbl_architecture.c.id))
1621
1622         mapper(Archive, self.tbl_archive,
1623                properties = dict(archive_id = self.tbl_archive.c.id,
1624                                  archive_name = self.tbl_archive.c.name))
1625
1626         mapper(BinAssociation, self.tbl_bin_associations,
1627                properties = dict(ba_id = self.tbl_bin_associations.c.id,
1628                                  suite_id = self.tbl_bin_associations.c.suite,
1629                                  suite = relation(Suite),
1630                                  binary_id = self.tbl_bin_associations.c.bin,
1631                                  binary = relation(DBBinary)))
1632
1633         mapper(DBBinary, self.tbl_binaries,
1634                properties = dict(binary_id = self.tbl_binaries.c.id,
1635                                  package = self.tbl_binaries.c.package,
1636                                  version = self.tbl_binaries.c.version,
1637                                  maintainer_id = self.tbl_binaries.c.maintainer,
1638                                  maintainer = relation(Maintainer),
1639                                  source_id = self.tbl_binaries.c.source,
1640                                  source = relation(DBSource),
1641                                  arch_id = self.tbl_binaries.c.architecture,
1642                                  architecture = relation(Architecture),
1643                                  poolfile_id = self.tbl_binaries.c.file,
1644                                  poolfile = relation(PoolFile),
1645                                  binarytype = self.tbl_binaries.c.type,
1646                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
1647                                  fingerprint = relation(Fingerprint),
1648                                  install_date = self.tbl_binaries.c.install_date,
1649                                  binassociations = relation(BinAssociation,
1650                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
1651
1652         mapper(Component, self.tbl_component,
1653                properties = dict(component_id = self.tbl_component.c.id,
1654                                  component_name = self.tbl_component.c.name))
1655
1656         mapper(DBConfig, self.tbl_config,
1657                properties = dict(config_id = self.tbl_config.c.id))
1658
1659         mapper(ContentAssociation, self.tbl_content_associations,
1660                properties = dict(ca_id = self.tbl_content_associations.c.id,
1661                                  filename_id = self.tbl_content_associations.c.filename,
1662                                  filename    = relation(ContentFilename),
1663                                  filepath_id = self.tbl_content_associations.c.filepath,
1664                                  filepath    = relation(ContentFilepath),
1665                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
1666                                  binary      = relation(DBBinary)))
1667
1668
1669         mapper(ContentFilename, self.tbl_content_file_names,
1670                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
1671                                  filename = self.tbl_content_file_names.c.file))
1672
1673         mapper(ContentFilepath, self.tbl_content_file_paths,
1674                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
1675                                  filepath = self.tbl_content_file_paths.c.path))
1676
1677         mapper(DSCFile, self.tbl_dsc_files,
1678                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
1679                                  source_id = self.tbl_dsc_files.c.source,
1680                                  source = relation(DBSource),
1681                                  poolfile_id = self.tbl_dsc_files.c.file,
1682                                  poolfile = relation(PoolFile)))
1683
1684         mapper(PoolFile, self.tbl_files,
1685                properties = dict(file_id = self.tbl_files.c.id,
1686                                  filesize = self.tbl_files.c.size,
1687                                  location_id = self.tbl_files.c.location,
1688                                  location = relation(Location)))
1689
1690         mapper(Fingerprint, self.tbl_fingerprint,
1691                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
1692                                  uid_id = self.tbl_fingerprint.c.uid,
1693                                  uid = relation(Uid),
1694                                  keyring_id = self.tbl_fingerprint.c.keyring,
1695                                  keyring = relation(Keyring)))
1696
1697         mapper(Keyring, self.tbl_keyrings,
1698                properties = dict(keyring_name = self.tbl_keyrings.c.name,
1699                                  keyring_id = self.tbl_keyrings.c.id))
1700
1701         mapper(Location, self.tbl_location,
1702                properties = dict(location_id = self.tbl_location.c.id,
1703                                  component_id = self.tbl_location.c.component,
1704                                  component = relation(Component),
1705                                  archive_id = self.tbl_location.c.archive,
1706                                  archive = relation(Archive),
1707                                  archive_type = self.tbl_location.c.type))
1708
1709         mapper(Maintainer, self.tbl_maintainer,
1710                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
1711
1712         mapper(NewComment, self.tbl_new_comments,
1713                properties = dict(comment_id = self.tbl_new_comments.c.id))
1714
1715         mapper(Override, self.tbl_override,
1716                properties = dict(suite_id = self.tbl_override.c.suite,
1717                                  suite = relation(Suite),
1718                                  component_id = self.tbl_override.c.component,
1719                                  component = relation(Component),
1720                                  priority_id = self.tbl_override.c.priority,
1721                                  priority = relation(Priority),
1722                                  section_id = self.tbl_override.c.section,
1723                                  section = relation(Section),
1724                                  overridetype_id = self.tbl_override.c.type,
1725                                  overridetype = relation(OverrideType)))
1726
1727         mapper(OverrideType, self.tbl_override_type,
1728                properties = dict(overridetype = self.tbl_override_type.c.type,
1729                                  overridetype_id = self.tbl_override_type.c.id))
1730
1731         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
1732                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
1733                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
1734                                  filepath = relation(ContentFilepath),
1735                                  filename_id = self.tbl_pending_content_associations.c.filename,
1736                                  filename = relation(ContentFilename)))
1737
1738         mapper(Priority, self.tbl_priority,
1739                properties = dict(priority_id = self.tbl_priority.c.id))
1740
1741         mapper(Queue, self.tbl_queue,
1742                properties = dict(queue_id = self.tbl_queue.c.id))
1743
1744         mapper(QueueBuild, self.tbl_queue_build,
1745                properties = dict(suite_id = self.tbl_queue_build.c.suite,
1746                                  queue_id = self.tbl_queue_build.c.queue,
1747                                  queue = relation(Queue, backref='queuebuild')))
1748
1749         mapper(Section, self.tbl_section,
1750                properties = dict(section_id = self.tbl_section.c.id))
1751
1752         mapper(DBSource, self.tbl_source,
1753                properties = dict(source_id = self.tbl_source.c.id,
1754                                  version = self.tbl_source.c.version,
1755                                  maintainer_id = self.tbl_source.c.maintainer,
1756                                  maintainer = relation(Maintainer,
1757                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
1758                                  poolfile_id = self.tbl_source.c.file,
1759                                  poolfile = relation(PoolFile),
1760                                  fingerprint_id = self.tbl_source.c.sig_fpr,
1761                                  fingerprint = relation(Fingerprint),
1762                                  changedby_id = self.tbl_source.c.changedby,
1763                                  changedby = relation(Maintainer,
1764                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
1765                                  srcfiles = relation(DSCFile,
1766                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
1767                                  srcassociations = relation(SrcAssociation,
1768                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
1769
1770         mapper(SrcAssociation, self.tbl_src_associations,
1771                properties = dict(sa_id = self.tbl_src_associations.c.id,
1772                                  suite_id = self.tbl_src_associations.c.suite,
1773                                  suite = relation(Suite),
1774                                  source_id = self.tbl_src_associations.c.source,
1775                                  source = relation(DBSource)))
1776
1777         mapper(SrcUploader, self.tbl_src_uploaders,
1778                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
1779                                  source_id = self.tbl_src_uploaders.c.source,
1780                                  source = relation(DBSource,
1781                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
1782                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
1783                                  maintainer = relation(Maintainer,
1784                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
1785
1786         mapper(Suite, self.tbl_suite,
1787                properties = dict(suite_id = self.tbl_suite.c.id))
1788
1789         mapper(SuiteArchitecture, self.tbl_suite_architectures,
1790                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
1791                                  suite = relation(Suite, backref='suitearchitectures'),
1792                                  arch_id = self.tbl_suite_architectures.c.architecture,
1793                                  architecture = relation(Architecture)))
1794
1795         mapper(Uid, self.tbl_uid,
1796                properties = dict(uid_id = self.tbl_uid.c.id,
1797                                  fingerprint = relation(Fingerprint)))
1798
1799     ## Connection functions
1800     def __createconn(self):
1801         from config import Config
1802         cnf = Config()
1803         if cnf["DB::Host"]:
1804             # TCP/IP
1805             connstr = "postgres://%s" % cnf["DB::Host"]
1806             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1807                 connstr += ":%s" % cnf["DB::Port"]
1808             connstr += "/%s" % cnf["DB::Name"]
1809         else:
1810             # Unix Socket
1811             connstr = "postgres:///%s" % cnf["DB::Name"]
1812             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1813                 connstr += "?port=%s" % cnf["DB::Port"]
1814
1815         self.db_pg   = create_engine(connstr, echo=self.debug)
1816         self.db_meta = MetaData()
1817         self.db_meta.bind = self.db_pg
1818         self.db_smaker = sessionmaker(bind=self.db_pg,
1819                                       autoflush=True,
1820                                       autocommit=False)
1821
1822         self.__setuptables()
1823         self.__setupmappers()
1824
1825     def session(self):
1826         return self.db_smaker()
1827
1828 __all__.append('DBConn')
1829
1830