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