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