]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
Merge commit 'mhy/master' into merge
[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     privatetrans = False
95
96     if session is None:
97         session = DBConn().session()
98         privatetrans = True
99
100     q = session.query(Architecture).filter_by(arch_string=architecture)
101
102     if q.count() == 0:
103         ret = None
104     else:
105         ret = q.one()
106
107     if privatetrans:
108         session.close()
109
110     return ret
111
112 __all__.append('get_architecture')
113
114 def get_architecture_suites(architecture, session=None):
115     """
116     Returns list of Suite objects for given C{architecture} name
117
118     @type source: str
119     @param source: Architecture name to search for
120
121     @type session: Session
122     @param session: Optional SQL session object (a temporary one will be
123     generated if not supplied)
124
125     @rtype: list
126     @return: list of Suite objects for the given name (may be empty)
127     """
128     privatetrans = False
129
130     if session is None:
131         session = DBConn().session()
132         privatetrans = True
133
134     q = session.query(Suite)
135     q = q.join(SuiteArchitecture)
136     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
137
138     ret = q.all()
139
140     if privatetrans:
141         session.close()
142
143     return ret
144
145 __all__.append('get_architecture_suites')
146
147 ################################################################################
148
149 class Archive(object):
150     def __init__(self, *args, **kwargs):
151         pass
152
153     def __repr__(self):
154         return '<Archive %s>' % self.name
155
156 __all__.append('Archive')
157
158 def get_archive(archive, session=None):
159     """
160     returns database id for given c{archive}.
161
162     @type archive: string
163     @param archive: the name of the arhive
164
165     @type session: Session
166     @param session: Optional SQLA session object (a temporary one will be
167     generated if not supplied)
168
169     @rtype: Archive
170     @return: Archive object for the given name (None if not present)
171
172     """
173     archive = archive.lower()
174
175     privatetrans = False
176     if session is None:
177         session = DBConn().session()
178         privatetrans = True
179
180     q = session.query(Archive).filter_by(archive_name=archive)
181
182     if q.count() == 0:
183         ret = None
184     else:
185         ret = q.one()
186
187     if privatetrans:
188         session.close()
189
190     return ret
191
192 __all__.append('get_archive')
193
194 ################################################################################
195
196 class BinAssociation(object):
197     def __init__(self, *args, **kwargs):
198         pass
199
200     def __repr__(self):
201         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
202
203 __all__.append('BinAssociation')
204
205 ################################################################################
206
207 class DBBinary(object):
208     def __init__(self, *args, **kwargs):
209         pass
210
211     def __repr__(self):
212         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
213
214 __all__.append('DBBinary')
215
216 def get_suites_binary_in(package, session=None):
217     """
218     Returns list of Suite objects which given C{package} name is in
219
220     @type source: str
221     @param source: DBBinary package name to search for
222
223     @rtype: list
224     @return: list of Suite objects for the given package
225     """
226
227     privatetrans = False
228     if session is None:
229         session = DBConn().session()
230         privatetrans = True
231
232     ret = session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
233
234     session.close()
235
236     return ret
237
238 __all__.append('get_suites_binary_in')
239
240 def get_binary_from_id(id, session=None):
241     """
242     Returns DBBinary object for given C{id}
243
244     @type id: int
245     @param id: Id of the required binary
246
247     @type session: Session
248     @param session: Optional SQLA session object (a temporary one will be
249     generated if not supplied)
250
251     @rtype: DBBinary
252     @return: DBBinary object for the given binary (None if not present)
253     """
254     privatetrans = False
255     if session is None:
256         session = DBConn().session()
257         privatetrans = True
258
259     q = session.query(DBBinary).filter_by(binary_id=id)
260
261     if q.count() == 0:
262         ret = None
263     else:
264         ret = q.one()
265
266     if privatetrans:
267         session.close()
268
269     return ret
270
271 __all__.append('get_binary_from_id')
272
273 def get_binaries_from_name(package, version=None, architecture=None, session=None):
274     """
275     Returns list of DBBinary objects for given C{package} name
276
277     @type package: str
278     @param package: DBBinary package name to search for
279
280     @type version: str or None
281     @param version: Version to search for (or None)
282
283     @type package: str, list or None
284     @param package: Architectures to limit to (or None if no limit)
285
286     @type session: Session
287     @param session: Optional SQL session object (a temporary one will be
288     generated if not supplied)
289
290     @rtype: list
291     @return: list of DBBinary objects for the given name (may be empty)
292     """
293     privatetrans = False
294     if session is None:
295         session = DBConn().session()
296         privatetrans = True
297
298     q = session.query(DBBinary).filter_by(package=package)
299
300     if version is not None:
301         q = q.filter_by(version=version)
302
303     if architecture is not None:
304         if not isinstance(architecture, list):
305             architecture = [architecture]
306         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
307
308     ret = q.all()
309
310     if privatetrans:
311         session.close()
312
313     return ret
314
315 __all__.append('get_binaries_from_name')
316
317 def get_binaries_from_source_id(source_id, session=None):
318     """
319     Returns list of DBBinary objects for given C{source_id}
320
321     @type source_id: int
322     @param source_id: source_id to search for
323
324     @type session: Session
325     @param session: Optional SQL session object (a temporary one will be
326     generated if not supplied)
327
328     @rtype: list
329     @return: list of DBBinary objects for the given name (may be empty)
330     """
331     privatetrans = False
332     if session is None:
333         session = DBConn().session()
334         privatetrans = True
335
336     ret = session.query(DBBinary).filter_by(source_id=source_id).all()
337
338     if privatetrans:
339         session.close()
340
341     return ret
342
343
344 __all__.append('get_binaries_from_source_id')
345
346
347 def get_binary_from_name_suite(package, suitename, session=None):
348     ### For dak examine-package
349     ### XXX: Doesn't use object API yet
350     privatetrans = False
351     if session is None:
352         session = DBConn().session()
353         privatetrans = True
354
355     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
356              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
357              WHERE b.package=:package
358                AND b.file = fi.id
359                AND fi.location = l.id
360                AND l.component = c.id
361                AND ba.bin=b.id
362                AND ba.suite = su.id
363                AND su.suite_name=:suitename
364           ORDER BY b.version DESC"""
365
366     ret = session.execute(sql, {'package': package, 'suitename': suitename})
367
368     if privatetrans:
369         session.close()
370
371     return ret
372
373 __all__.append('get_binary_from_name_suite')
374
375 def get_binary_components(package, suitename, arch, session=None):
376     # Check for packages that have moved from one component to another
377     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
378     WHERE b.package=:package AND s.suite_name=:suitename
379       AND (a.arch_string = :arch OR a.arch_string = 'all')
380       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
381       AND f.location = l.id
382       AND l.component = c.id
383       AND b.file = f.id"""
384
385     vals = {'package': package, 'suitename': suitename, 'arch': arch}
386
387     privatetrans = False
388     if session is None:
389         session = DBConn().session()
390         privatetrans = True
391
392     ret = session.execute(query, vals)
393
394     if privatetrans:
395         session.close()
396
397     return ret
398
399 __all__.append('get_binary_components')
400
401 ################################################################################
402
403 class Component(object):
404     def __init__(self, *args, **kwargs):
405         pass
406
407     def __eq__(self, val):
408         if isinstance(val, str):
409             return (self.component_name == val)
410         # This signals to use the normal comparison operator
411         return NotImplemented
412
413     def __ne__(self, val):
414         if isinstance(val, str):
415             return (self.component_name != val)
416         # This signals to use the normal comparison operator
417         return NotImplemented
418
419     def __repr__(self):
420         return '<Component %s>' % self.component_name
421
422
423 __all__.append('Component')
424
425 def get_component(component, session=None):
426     """
427     Returns database id for given C{component}.
428
429     @type component: string
430     @param component: The name of the override type
431
432     @rtype: int
433     @return: the database id for the given component
434
435     """
436     component = component.lower()
437
438     privatetrans = False
439     if session is None:
440         session = DBConn().session()
441         privatetrans = True
442
443     q = session.query(Component).filter_by(component_name=component)
444
445     if q.count() == 0:
446         ret = None
447     else:
448         ret = q.one()
449
450     if privatetrans:
451         session.close()
452
453     return ret
454
455 __all__.append('get_component')
456
457 ################################################################################
458
459 class DBConfig(object):
460     def __init__(self, *args, **kwargs):
461         pass
462
463     def __repr__(self):
464         return '<DBConfig %s>' % self.name
465
466 __all__.append('DBConfig')
467
468 ################################################################################
469
470 class ContentFilename(object):
471     def __init__(self, *args, **kwargs):
472         pass
473
474     def __repr__(self):
475         return '<ContentFilename %s>' % self.filename
476
477 __all__.append('ContentFilename')
478
479 def get_or_set_contents_file_id(filename, session=None):
480     """
481     Returns database id for given filename.
482
483     If no matching file is found, a row is inserted.
484
485     @type filename: string
486     @param filename: The filename
487     @type session: SQLAlchemy
488     @param session: Optional SQL session object (a temporary one will be
489     generated if not supplied).  If not passed, a commit will be performed at
490     the end of the function, otherwise the caller is responsible for commiting.
491
492     @rtype: int
493     @return: the database id for the given component
494     """
495     privatetrans = False
496     if session is None:
497         session = DBConn().session()
498         privatetrans = True
499
500     q = session.query(ContentFilename).filter_by(filename=filename)
501     if q.count() < 1:
502         cf = ContentFilename()
503         cf.filename = filename
504         session.add(cf)
505         if privatetrans:
506             session.commit()
507         else:
508             session.flush()
509         ret = cf.cafilename_id
510     else:
511         ret = q.one().cafilename_id
512
513     if privatetrans:
514         session.close()
515
516     return ret
517
518 __all__.append('get_or_set_contents_file_id')
519
520 def get_contents(suite, overridetype, section=None, session=None):
521     """
522     Returns contents for a suite / overridetype combination, limiting
523     to a section if not None.
524
525     @type suite: Suite
526     @param suite: Suite object
527
528     @type overridetype: OverrideType
529     @param overridetype: OverrideType object
530
531     @type section: Section
532     @param section: Optional section object to limit results to
533
534     @type session: SQLAlchemy
535     @param session: Optional SQL session object (a temporary one will be
536     generated if not supplied)
537
538     @rtype: ResultsProxy
539     @return: ResultsProxy object set up to return tuples of (filename, section,
540     package, arch_id)
541     """
542
543     privatetrans = False
544     if session is None:
545         session = DBConn().session()
546         privatetrans = True
547
548     # find me all of the contents for a given suite
549     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
550                             s.section,
551                             b.package,
552                             b.architecture
553                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
554                    JOIN content_file_names n ON (c.filename=n.id)
555                    JOIN binaries b ON (b.id=c.binary_pkg)
556                    JOIN override o ON (o.package=b.package)
557                    JOIN section s ON (s.id=o.section)
558                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
559                    AND b.type=:overridetypename"""
560
561     vals = {'suiteid': suite.suite_id,
562             'overridetypeid': overridetype.overridetype_id,
563             'overridetypename': overridetype.overridetype}
564
565     if section is not None:
566         contents_q += " AND s.id = :sectionid"
567         vals['sectionid'] = section.section_id
568
569     contents_q += " ORDER BY fn"
570
571     ret = session.execute(contents_q, vals)
572
573     if privatetrans:
574         session.close()
575
576     return ret
577
578 __all__.append('get_contents')
579
580 ################################################################################
581
582 class ContentFilepath(object):
583     def __init__(self, *args, **kwargs):
584         pass
585
586     def __repr__(self):
587         return '<ContentFilepath %s>' % self.filepath
588
589 __all__.append('ContentFilepath')
590
591 def get_or_set_contents_path_id(filepath, session=None):
592     """
593     Returns database id for given path.
594
595     If no matching file is found, a row is inserted.
596
597     @type filename: string
598     @param filename: The filepath
599     @type session: SQLAlchemy
600     @param session: Optional SQL session object (a temporary one will be
601     generated if not supplied).  If not passed, a commit will be performed at
602     the end of the function, otherwise the caller is responsible for commiting.
603
604     @rtype: int
605     @return: the database id for the given path
606     """
607     privatetrans = False
608     if session is None:
609         session = DBConn().session()
610         privatetrans = True
611
612     q = session.query(ContentFilepath).filter_by(filepath=filepath)
613     if q.count() < 1:
614         cf = ContentFilepath()
615         cf.filepath = filepath
616         session.add(cf)
617         if privatetrans:
618             session.commit()
619         else:
620             session.flush()
621         ret = cf.cafilepath_id
622     else:
623         ret = q.one().cafilepath_id
624
625     if privatetrans:
626         session.close()
627
628     return ret
629
630 __all__.append('get_or_set_contents_path_id')
631
632 ################################################################################
633
634 class ContentAssociation(object):
635     def __init__(self, *args, **kwargs):
636         pass
637
638     def __repr__(self):
639         return '<ContentAssociation %s>' % self.ca_id
640
641 __all__.append('ContentAssociation')
642
643 def insert_content_paths(binary_id, fullpaths, session=None):
644     """
645     Make sure given path is associated with given binary id
646
647     @type binary_id: int
648     @param binary_id: the id of the binary
649     @type fullpaths: list
650     @param fullpaths: the list of paths of the file being associated with the binary
651     @type session: SQLAlchemy session
652     @param session: Optional SQLAlchemy session.  If this is passed, the caller
653     is responsible for ensuring a transaction has begun and committing the
654     results or rolling back based on the result code.  If not passed, a commit
655     will be performed at the end of the function, otherwise the caller is
656     responsible for commiting.
657
658     @return: True upon success
659     """
660
661     privatetrans = False
662     if session is None:
663         session = DBConn().session()
664         privatetrans = True
665
666     try:
667         # Insert paths
668         pathcache = {}
669         for fullpath in fullpaths:
670             # Get the necessary IDs ...
671             (path, file) = os.path.split(fullpath)
672
673             filepath_id = get_or_set_contents_path_id(path, session)
674             filename_id = get_or_set_contents_file_id(file, session)
675
676             pathcache[fullpath] = (filepath_id, filename_id)
677
678         for fullpath, dat in pathcache.items():
679             ca = ContentAssociation()
680             ca.binary_id = binary_id
681             ca.filepath_id = dat[0]
682             ca.filename_id = dat[1]
683             session.add(ca)
684
685         # Only commit if we set up the session ourself
686         if privatetrans:
687             session.commit()
688             session.close()
689         else:
690             session.flush()
691
692         return True
693
694     except:
695         traceback.print_exc()
696
697         # Only rollback if we set up the session ourself
698         if privatetrans:
699             session.rollback()
700             session.close()
701
702         return False
703
704 __all__.append('insert_content_paths')
705
706 ################################################################################
707
708 class DSCFile(object):
709     def __init__(self, *args, **kwargs):
710         pass
711
712     def __repr__(self):
713         return '<DSCFile %s>' % self.dscfile_id
714
715 __all__.append('DSCFile')
716
717 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
718     """
719     Returns a list of DSCFiles which may be empty
720
721     @type dscfile_id: int (optional)
722     @param dscfile_id: the dscfile_id of the DSCFiles to find
723
724     @type source_id: int (optional)
725     @param source_id: the source id related to the DSCFiles to find
726
727     @type poolfile_id: int (optional)
728     @param poolfile_id: the poolfile id related to the DSCFiles to find
729
730     @rtype: list
731     @return: Possibly empty list of DSCFiles
732     """
733
734     privatetrans = False
735     if session is None:
736         session = DBConn().session()
737         privatetrans = True
738
739     q = session.query(DSCFile)
740
741     if dscfile_id is not None:
742         q = q.filter_by(dscfile_id=dscfile_id)
743
744     if source_id is not None:
745         q = q.filter_by(source_id=source_id)
746
747     if poolfile_id is not None:
748         q = q.filter_by(poolfile_id=poolfile_id)
749
750     ret = q.all()
751
752     if privatetrans:
753         session.close()
754
755     return ret
756
757 __all__.append('get_dscfiles')
758
759 ################################################################################
760
761 class PoolFile(object):
762     def __init__(self, *args, **kwargs):
763         pass
764
765     def __repr__(self):
766         return '<PoolFile %s>' % self.filename
767
768 __all__.append('PoolFile')
769
770 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
771     """
772     Returns a tuple:
773      (ValidFileFound [boolean or None], PoolFile object or None)
774
775     @type filename: string
776     @param filename: the filename of the file to check against the DB
777
778     @type filesize: int
779     @param filesize: the size of the file to check against the DB
780
781     @type md5sum: string
782     @param md5sum: the md5sum of the file to check against the DB
783
784     @type location_id: int
785     @param location_id: the id of the location to look in
786
787     @rtype: tuple
788     @return: Tuple of length 2.
789              If more than one file found with that name:
790                     (None,  None)
791              If valid pool file found: (True, PoolFile object)
792              If valid pool file not found:
793                     (False, None) if no file found
794                     (False, PoolFile object) if file found with size/md5sum mismatch
795     """
796
797     privatetrans = False
798     if session is None:
799         session = DBConn().session()
800         privatetrans = True
801
802     q = session.query(PoolFile).filter_by(filename=filename)
803     q = q.join(Location).filter_by(location_id=location_id)
804
805     ret = None
806
807     if q.count() > 1:
808         ret = (None, None)
809     elif q.count() < 1:
810         ret = (False, None)
811     else:
812         obj = q.one()
813         if obj.md5sum != md5sum or obj.filesize != filesize:
814             ret = (False, obj)
815
816     if ret is None:
817         ret = (True, obj)
818
819     if privatetrans:
820         session.close()
821
822     return ret
823
824 __all__.append('check_poolfile')
825
826 def get_poolfile_by_id(file_id, session=None):
827     """
828     Returns a PoolFile objects or None for the given id
829
830     @type file_id: int
831     @param file_id: the id of the file to look for
832
833     @rtype: PoolFile or None
834     @return: either the PoolFile object or None
835     """
836
837     privatetrans = False
838     if session is None:
839         session = DBConn().session()
840         privatetrans = True
841
842     q = session.query(PoolFile).filter_by(file_id=file_id)
843
844     if q.count() > 0:
845         ret = q.one()
846     else:
847         ret = None
848
849     if privatetrans:
850         session.close()
851
852     return ret
853
854 __all__.append('get_poolfile_by_id')
855
856
857 def get_poolfile_by_name(filename, location_id=None, session=None):
858     """
859     Returns an array of PoolFile objects for the given filename and
860     (optionally) location_id
861
862     @type filename: string
863     @param filename: the filename of the file to check against the DB
864
865     @type location_id: int
866     @param location_id: the id of the location to look in (optional)
867
868     @rtype: array
869     @return: array of PoolFile objects
870     """
871
872     privatetrans = False
873     if session is None:
874         session = DBConn().session()
875         privatetrans = True
876
877     q = session.query(PoolFile).filter_by(filename=filename)
878
879     if location_id is not None:
880         q = q.join(Location).filter_by(location_id=location_id)
881
882     ret = q.all()
883
884     if privatetrans:
885         session.close()
886
887     return ret
888
889 __all__.append('get_poolfile_by_name')
890
891 def get_poolfile_like_name(filename, session=None):
892     """
893     Returns an array of PoolFile objects which are like the given name
894
895     @type filename: string
896     @param filename: the filename of the file to check against the DB
897
898     @rtype: array
899     @return: array of PoolFile objects
900     """
901
902     privatetrans = False
903     if session is None:
904         session = DBConn().session()
905         privatetrans = True
906
907     # TODO: There must be a way of properly using bind parameters with %FOO%
908     q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
909
910     ret = q.all()
911
912     if privatetrans:
913         session.close()
914
915     return ret
916
917 __all__.append('get_poolfile_like_name')
918
919 ################################################################################
920
921 class Fingerprint(object):
922     def __init__(self, *args, **kwargs):
923         pass
924
925     def __repr__(self):
926         return '<Fingerprint %s>' % self.fingerprint
927
928 __all__.append('Fingerprint')
929
930 def get_or_set_fingerprint(fpr, session=None):
931     """
932     Returns Fingerprint object for given fpr.
933
934     If no matching fpr is found, a row is inserted.
935
936     @type fpr: string
937     @param fpr: The fpr to find / add
938
939     @type session: SQLAlchemy
940     @param session: Optional SQL session object (a temporary one will be
941     generated if not supplied).  If not passed, a commit will be performed at
942     the end of the function, otherwise the caller is responsible for commiting.
943     A flush will be performed either way.
944
945     @rtype: Fingerprint
946     @return: the Fingerprint object for the given fpr
947     """
948     privatetrans = False
949     if session is None:
950         session = DBConn().session()
951         privatetrans = True
952
953     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
954     if q.count() < 1:
955         fingerprint = Fingerprint()
956         fingerprint.fingerprint = fpr
957         session.add(fingerprint)
958         if privatetrans:
959             session.commit()
960         else:
961             session.flush()
962         ret = fingerprint
963     else:
964         ret = q.one()
965
966     if privatetrans:
967         session.close()
968
969     return ret
970
971 __all__.append('get_or_set_fingerprint')
972
973 ################################################################################
974
975 class Keyring(object):
976     def __init__(self, *args, **kwargs):
977         pass
978
979     def __repr__(self):
980         return '<Keyring %s>' % self.keyring_name
981
982 __all__.append('Keyring')
983
984 def get_or_set_keyring(keyring, session=None):
985     """
986     If C{keyring} does not have an entry in the C{keyrings} table yet, create one
987     and return the new Keyring
988     If C{keyring} already has an entry, simply return the existing Keyring
989
990     @type keyring: string
991     @param keyring: the keyring name
992
993     @rtype: Keyring
994     @return: the Keyring object for this keyring
995
996     """
997     privatetrans = False
998     if session is None:
999         session = DBConn().session()
1000         privatetrans = True
1001
1002     try:
1003         obj = session.query(Keyring).filter_by(keyring_name=keyring).first()
1004
1005         if obj is None:
1006             obj = Keyring(keyring_name=keyring)
1007             session.add(obj)
1008             if privatetrans:
1009                 session.commit()
1010             else:
1011                 session.flush()
1012
1013         return obj
1014     finally:
1015         if privatetrans:
1016             session.close()
1017
1018 __all__.append('get_or_set_keyring')
1019
1020 ################################################################################
1021
1022 class Location(object):
1023     def __init__(self, *args, **kwargs):
1024         pass
1025
1026     def __repr__(self):
1027         return '<Location %s (%s)>' % (self.path, self.location_id)
1028
1029 __all__.append('Location')
1030
1031 def get_location(location, component=None, archive=None, session=None):
1032     """
1033     Returns Location object for the given combination of location, component
1034     and archive
1035
1036     @type location: string
1037     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1038
1039     @type component: string
1040     @param component: the component name (if None, no restriction applied)
1041
1042     @type archive: string
1043     @param archive_id: the archive name (if None, no restriction applied)
1044
1045     @rtype: Location / None
1046     @return: Either a Location object or None if one can't be found
1047     """
1048
1049     privatetrans = False
1050     if session is None:
1051         session = DBConn().session()
1052         privatetrans = True
1053
1054     q = session.query(Location).filter_by(path=location)
1055
1056     if archive is not None:
1057         q = q.join(Archive).filter_by(archive_name=archive)
1058
1059     if component is not None:
1060         q = q.join(Component).filter_by(component_name=component)
1061
1062     if q.count() < 1:
1063         ret = None
1064     else:
1065         ret = q.one()
1066
1067     if privatetrans:
1068         session.close()
1069
1070     return ret
1071
1072 __all__.append('get_location')
1073
1074 ################################################################################
1075
1076 class Maintainer(object):
1077     def __init__(self, *args, **kwargs):
1078         pass
1079
1080     def __repr__(self):
1081         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1082
1083     def get_split_maintainer(self):
1084         if not hasattr(self, 'name') or self.name is None:
1085             return ('', '', '', '')
1086
1087         return fix_maintainer(self.name.strip())
1088
1089 __all__.append('Maintainer')
1090
1091 def get_or_set_maintainer(name, session=None):
1092     """
1093     Returns Maintainer object for given maintainer name.
1094
1095     If no matching maintainer name is found, a row is inserted.
1096
1097     @type name: string
1098     @param name: The maintainer name to add
1099
1100     @type session: SQLAlchemy
1101     @param session: Optional SQL session object (a temporary one will be
1102     generated if not supplied).  If not passed, a commit will be performed at
1103     the end of the function, otherwise the caller is responsible for commiting.
1104     A flush will be performed either way.
1105
1106     @rtype: Maintainer
1107     @return: the Maintainer object for the given maintainer
1108     """
1109     privatetrans = False
1110     if session is None:
1111         session = DBConn().session()
1112         privatetrans = True
1113
1114     q = session.query(Maintainer).filter_by(name=name)
1115     if q.count() < 1:
1116         maintainer = Maintainer()
1117         maintainer.name = name
1118         session.add(maintainer)
1119         if privatetrans:
1120             session.commit()
1121         else:
1122             session.flush()
1123         ret = maintainer
1124     else:
1125         ret = q.one()
1126
1127     if privatetrans:
1128         session.close()
1129
1130     return ret
1131
1132 __all__.append('get_or_set_maintainer')
1133
1134 def get_maintainer(maintainer_id, session=None):
1135     """
1136     Return the name of the maintainer behind C{maintainer_id} or None if that
1137     maintainer_id is invalid.
1138
1139     @type maintainer_id: int
1140     @param maintainer_id: the id of the maintainer
1141
1142     @rtype: Maintainer
1143     @return: the Maintainer with this C{maintainer_id}
1144     """
1145
1146     privatetrans = False
1147     if session is None:
1148         session = DBConn().session()
1149         privatetrans = True
1150
1151     try:
1152         return session.query(Maintainer).get(maintainer_id)
1153     finally:
1154         if privatetrans:
1155             session.close()
1156
1157 __all__.append('get_maintainer')
1158
1159 ################################################################################
1160
1161 class NewComment(object):
1162     def __init__(self, *args, **kwargs):
1163         pass
1164
1165     def __repr__(self):
1166         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1167
1168 __all__.append('NewComment')
1169
1170 def has_new_comment(package, version, session=None):
1171     """
1172     Returns true if the given combination of C{package}, C{version} has a comment.
1173
1174     @type package: string
1175     @param package: name of the package
1176
1177     @type version: string
1178     @param version: package version
1179
1180     @type session: Session
1181     @param session: Optional SQLA session object (a temporary one will be
1182     generated if not supplied)
1183
1184     @rtype: boolean
1185     @return: true/false
1186     """
1187
1188     privatetrans = False
1189     if session is None:
1190         session = DBConn().session()
1191         privatetrans = True
1192
1193     q = session.query(NewComment)
1194     q = q.filter_by(package=package)
1195     q = q.filter_by(version=version)
1196
1197     ret = q.count() > 0
1198
1199     if privatetrans:
1200         session.close()
1201
1202     return ret
1203
1204 __all__.append('has_new_comment')
1205
1206 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1207     """
1208     Returns (possibly empty) list of NewComment objects for the given
1209     parameters
1210
1211     @type package: string (optional)
1212     @param package: name of the package
1213
1214     @type version: string (optional)
1215     @param version: package version
1216
1217     @type comment_id: int (optional)
1218     @param comment_id: An id of a comment
1219
1220     @type session: Session
1221     @param session: Optional SQLA session object (a temporary one will be
1222     generated if not supplied)
1223
1224     @rtype: list
1225     @return: A (possibly empty) list of NewComment objects will be returned
1226
1227     """
1228
1229     privatetrans = False
1230     if session is None:
1231         session = DBConn().session()
1232         privatetrans = True
1233
1234     q = session.query(NewComment)
1235     if package is not None: q = q.filter_by(package=package)
1236     if version is not None: q = q.filter_by(version=version)
1237     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1238
1239     ret = q.all()
1240
1241     if privatetrans:
1242         session.close()
1243
1244     return ret
1245
1246 __all__.append('get_new_comments')
1247
1248 ################################################################################
1249
1250 class Override(object):
1251     def __init__(self, *args, **kwargs):
1252         pass
1253
1254     def __repr__(self):
1255         return '<Override %s (%s)>' % (self.package, self.suite_id)
1256
1257 __all__.append('Override')
1258
1259 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1260     """
1261     Returns Override object for the given parameters
1262
1263     @type package: string
1264     @param package: The name of the package
1265
1266     @type suite: string, list or None
1267     @param suite: The name of the suite (or suites if a list) to limit to.  If
1268                   None, don't limit.  Defaults to None.
1269
1270     @type component: string, list or None
1271     @param component: The name of the component (or components if a list) to
1272                       limit to.  If None, don't limit.  Defaults to None.
1273
1274     @type overridetype: string, list or None
1275     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1276                          limit to.  If None, don't limit.  Defaults to None.
1277
1278     @type session: Session
1279     @param session: Optional SQLA session object (a temporary one will be
1280     generated if not supplied)
1281
1282     @rtype: list
1283     @return: A (possibly empty) list of Override objects will be returned
1284
1285     """
1286     privatetrans = False
1287     if session is None:
1288         session = DBConn().session()
1289         privatetrans = True
1290
1291     q = session.query(Override)
1292     q = q.filter_by(package=package)
1293
1294     if suite is not None:
1295         if not isinstance(suite, list): suite = [suite]
1296         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1297
1298     if component is not None:
1299         if not isinstance(component, list): component = [component]
1300         q = q.join(Component).filter(Component.component_name.in_(component))
1301
1302     if overridetype is not None:
1303         if not isinstance(overridetype, list): overridetype = [overridetype]
1304         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1305
1306     ret = q.all()
1307
1308     if privatetrans:
1309         session.close()
1310
1311     return ret
1312
1313 __all__.append('get_override')
1314
1315
1316 ################################################################################
1317
1318 class OverrideType(object):
1319     def __init__(self, *args, **kwargs):
1320         pass
1321
1322     def __repr__(self):
1323         return '<OverrideType %s>' % self.overridetype
1324
1325 __all__.append('OverrideType')
1326
1327 def get_override_type(override_type, session=None):
1328     """
1329     Returns OverrideType object for given C{override type}.
1330
1331     @type override_type: string
1332     @param override_type: The name of the override type
1333
1334     @type session: Session
1335     @param session: Optional SQLA session object (a temporary one will be
1336     generated if not supplied)
1337
1338     @rtype: int
1339     @return: the database id for the given override type
1340
1341     """
1342     privatetrans = False
1343     if session is None:
1344         session = DBConn().session()
1345         privatetrans = True
1346
1347     q = session.query(OverrideType).filter_by(overridetype=override_type)
1348
1349     if q.count() == 0:
1350         ret = None
1351     else:
1352         ret = q.one()
1353
1354     if privatetrans:
1355         session.close()
1356
1357     return ret
1358
1359 __all__.append('get_override_type')
1360
1361 ################################################################################
1362
1363 class PendingContentAssociation(object):
1364     def __init__(self, *args, **kwargs):
1365         pass
1366
1367     def __repr__(self):
1368         return '<PendingContentAssociation %s>' % self.pca_id
1369
1370 __all__.append('PendingContentAssociation')
1371
1372 def insert_pending_content_paths(package, fullpaths, session=None):
1373     """
1374     Make sure given paths are temporarily associated with given
1375     package
1376
1377     @type package: dict
1378     @param package: the package to associate with should have been read in from the binary control file
1379     @type fullpaths: list
1380     @param fullpaths: the list of paths of the file being associated with the binary
1381     @type session: SQLAlchemy session
1382     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1383     is responsible for ensuring a transaction has begun and committing the
1384     results or rolling back based on the result code.  If not passed, a commit
1385     will be performed at the end of the function
1386
1387     @return: True upon success, False if there is a problem
1388     """
1389
1390     privatetrans = False
1391
1392     if session is None:
1393         session = DBConn().session()
1394         privatetrans = True
1395
1396     try:
1397         arch = get_architecture(package['Architecture'], session)
1398         arch_id = arch.arch_id
1399
1400         # Remove any already existing recorded files for this package
1401         q = session.query(PendingContentAssociation)
1402         q = q.filter_by(package=package['Package'])
1403         q = q.filter_by(version=package['Version'])
1404         q = q.filter_by(architecture=arch_id)
1405         q.delete()
1406
1407         # Insert paths
1408         pathcache = {}
1409         for fullpath in fullpaths:
1410             (path, file) = os.path.split(fullpath)
1411
1412             if path.startswith( "./" ):
1413                 path = path[2:]
1414
1415             filepath_id = get_or_set_contents_path_id(path, session)
1416             filename_id = get_or_set_contents_file_id(file, session)
1417
1418             pathcache[fullpath] = (filepath_id, filename_id)
1419
1420         for fullpath, dat in pathcache.items():
1421             pca = PendingContentAssociation()
1422             pca.package = package['Package']
1423             pca.version = package['Version']
1424             pca.filepath_id = dat[0]
1425             pca.filename_id = dat[1]
1426             pca.architecture = arch_id
1427             session.add(pca)
1428
1429         # Only commit if we set up the session ourself
1430         if privatetrans:
1431             session.commit()
1432             session.close()
1433         else:
1434             session.flush()
1435
1436         return True
1437     except Exception, e:
1438         traceback.print_exc()
1439
1440         # Only rollback if we set up the session ourself
1441         if privatetrans:
1442             session.rollback()
1443             session.close()
1444
1445         return False
1446
1447 __all__.append('insert_pending_content_paths')
1448
1449 ################################################################################
1450
1451 class Priority(object):
1452     def __init__(self, *args, **kwargs):
1453         pass
1454
1455     def __eq__(self, val):
1456         if isinstance(val, str):
1457             return (self.priority == val)
1458         # This signals to use the normal comparison operator
1459         return NotImplemented
1460
1461     def __ne__(self, val):
1462         if isinstance(val, str):
1463             return (self.priority != val)
1464         # This signals to use the normal comparison operator
1465         return NotImplemented
1466
1467     def __repr__(self):
1468         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1469
1470 __all__.append('Priority')
1471
1472 def get_priority(priority, session=None):
1473     """
1474     Returns Priority object for given C{priority name}.
1475
1476     @type priority: string
1477     @param priority: The name of the priority
1478
1479     @type session: Session
1480     @param session: Optional SQLA session object (a temporary one will be
1481     generated if not supplied)
1482
1483     @rtype: Priority
1484     @return: Priority object for the given priority
1485
1486     """
1487     privatetrans = False
1488     if session is None:
1489         session = DBConn().session()
1490         privatetrans = True
1491
1492     q = session.query(Priority).filter_by(priority=priority)
1493
1494     if q.count() == 0:
1495         ret = None
1496     else:
1497         ret = q.one()
1498
1499     if privatetrans:
1500         session.close()
1501
1502     return ret
1503
1504 __all__.append('get_priority')
1505
1506 def get_priorities(session=None):
1507     """
1508     Returns dictionary of priority names -> id mappings
1509
1510     @type session: Session
1511     @param session: Optional SQL session object (a temporary one will be
1512     generated if not supplied)
1513
1514     @rtype: dictionary
1515     @return: dictionary of priority names -> id mappings
1516     """
1517     privatetrans = False
1518     if session is None:
1519         session = DBConn().session()
1520         privatetrans = True
1521
1522     ret = {}
1523     q = session.query(Priority)
1524     for x in q.all():
1525         ret[x.priority] = x.priority_id
1526
1527     if privatetrans:
1528         session.close()
1529
1530     return ret
1531
1532 __all__.append('get_priorities')
1533
1534 ################################################################################
1535
1536 class Queue(object):
1537     def __init__(self, *args, **kwargs):
1538         pass
1539
1540     def __repr__(self):
1541         return '<Queue %s>' % self.queue_name
1542
1543     def autobuild_upload(self, changes, srcpath, session=None):
1544         """
1545         Update queue_build database table used for incoming autobuild support.
1546
1547         @type changes: Changes
1548         @param changes: changes object for the upload to process
1549
1550         @type srcpath: string
1551         @param srcpath: path for the queue file entries/link destinations
1552
1553         @type session: SQLAlchemy session
1554         @param session: Optional SQLAlchemy session.  If this is passed, the
1555         caller is responsible for ensuring a transaction has begun and
1556         committing the results or rolling back based on the result code.  If
1557         not passed, a commit will be performed at the end of the function,
1558         otherwise the caller is responsible for commiting.
1559
1560         @rtype: NoneType or string
1561         @return: None if the operation failed, a string describing the error if not
1562         """
1563
1564         privatetrans = False
1565         if session is None:
1566             session = DBConn().session()
1567             privatetrans = True
1568
1569         # TODO: Remove by moving queue config into the database
1570         conf = Config()
1571
1572         for suitename in changes.changes["distribution"].keys():
1573             # TODO: Move into database as:
1574             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1575             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1576             #       This also gets rid of the SecurityQueueBuild hack below
1577             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1578                 continue
1579
1580             # Find suite object
1581             s = get_suite(suitename, session)
1582             if s is None:
1583                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1584
1585             # TODO: Get from database as above
1586             dest_dir = conf["Dir::QueueBuild"]
1587
1588             # TODO: Move into database as above
1589             if conf.FindB("Dinstall::SecurityQueueBuild"):
1590                 dest_dir = os.path.join(dest_dir, suitename)
1591
1592             for file_entry in changes.files.keys():
1593                 src = os.path.join(srcpath, file_entry)
1594                 dest = os.path.join(dest_dir, file_entry)
1595
1596                 # TODO: Move into database as above
1597                 if conf.FindB("Dinstall::SecurityQueueBuild"):
1598                     # Copy it since the original won't be readable by www-data
1599                     utils.copy(src, dest)
1600                 else:
1601                     # Create a symlink to it
1602                     os.symlink(src, dest)
1603
1604                 qb = QueueBuild()
1605                 qb.suite_id = s.suite_id
1606                 qb.queue_id = self.queue_id
1607                 qb.filename = dest
1608                 qb.in_queue = True
1609
1610                 session.add(qb)
1611
1612             # If the .orig.tar.gz is in the pool, create a symlink to
1613             # it (if one doesn't already exist)
1614             if changes.orig_tar_id:
1615                 # Determine the .orig.tar.gz file name
1616                 for dsc_file in changes.dsc_files.keys():
1617                     if dsc_file.endswith(".orig.tar.gz"):
1618                         filename = dsc_file
1619
1620                 dest = os.path.join(dest_dir, filename)
1621
1622                 # If it doesn't exist, create a symlink
1623                 if not os.path.exists(dest):
1624                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1625                                         {'id': changes.orig_tar_id})
1626                     res = q.fetchone()
1627                     if not res:
1628                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1629
1630                     src = os.path.join(res[0], res[1])
1631                     os.symlink(src, dest)
1632
1633                     # Add it to the list of packages for later processing by apt-ftparchive
1634                     qb = QueueBuild()
1635                     qb.suite_id = s.suite_id
1636                     qb.queue_id = self.queue_id
1637                     qb.filename = dest
1638                     qb.in_queue = True
1639                     session.add(qb)
1640
1641                 # If it does, update things to ensure it's not removed prematurely
1642                 else:
1643                     qb = get_queue_build(dest, s.suite_id, session)
1644                     if qb is None:
1645                         qb.in_queue = True
1646                         qb.last_used = None
1647                         session.add(qb)
1648
1649         if privatetrans:
1650             session.commit()
1651             session.close()
1652
1653         return None
1654
1655 __all__.append('Queue')
1656
1657 def get_queue(queuename, session=None):
1658     """
1659     Returns Queue object for given C{queue name}.
1660
1661     @type queuename: string
1662     @param queuename: The name of the queue
1663
1664     @type session: Session
1665     @param session: Optional SQLA session object (a temporary one will be
1666     generated if not supplied)
1667
1668     @rtype: Queue
1669     @return: Queue object for the given queue
1670
1671     """
1672     privatetrans = False
1673     if session is None:
1674         session = DBConn().session()
1675         privatetrans = True
1676
1677     q = session.query(Queue).filter_by(queue_name=queuename)
1678     if q.count() == 0:
1679         ret = None
1680     else:
1681         ret = q.one()
1682
1683     if privatetrans:
1684         session.close()
1685
1686     return ret
1687
1688 __all__.append('get_queue')
1689
1690 ################################################################################
1691
1692 class QueueBuild(object):
1693     def __init__(self, *args, **kwargs):
1694         pass
1695
1696     def __repr__(self):
1697         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1698
1699 __all__.append('QueueBuild')
1700
1701 def get_queue_build(filename, suite, session=None):
1702     """
1703     Returns QueueBuild object for given C{filename} and C{suite}.
1704
1705     @type filename: string
1706     @param filename: The name of the file
1707
1708     @type suiteid: int or str
1709     @param suiteid: Suite name or ID
1710
1711     @type session: Session
1712     @param session: Optional SQLA session object (a temporary one will be
1713     generated if not supplied)
1714
1715     @rtype: Queue
1716     @return: Queue object for the given queue
1717
1718     """
1719     privatetrans = False
1720     if session is None:
1721         session = DBConn().session()
1722         privatetrans = True
1723
1724     if isinstance(suite, int):
1725         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1726     else:
1727         q = session.query(QueueBuild).filter_by(filename=filename)
1728         q = q.join(Suite).filter_by(suite_name=suite)
1729
1730     if q.count() == 0:
1731         ret = None
1732     else:
1733         ret = q.one()
1734
1735     if privatetrans:
1736         session.close()
1737
1738     return ret
1739
1740 __all__.append('get_queue_build')
1741
1742 ################################################################################
1743
1744 class Section(object):
1745     def __init__(self, *args, **kwargs):
1746         pass
1747
1748     def __eq__(self, val):
1749         if isinstance(val, str):
1750             return (self.section == val)
1751         # This signals to use the normal comparison operator
1752         return NotImplemented
1753
1754     def __ne__(self, val):
1755         if isinstance(val, str):
1756             return (self.section != val)
1757         # This signals to use the normal comparison operator
1758         return NotImplemented
1759
1760     def __repr__(self):
1761         return '<Section %s>' % self.section
1762
1763 __all__.append('Section')
1764
1765 def get_section(section, session=None):
1766     """
1767     Returns Section object for given C{section name}.
1768
1769     @type section: string
1770     @param section: The name of the section
1771
1772     @type session: Session
1773     @param session: Optional SQLA session object (a temporary one will be
1774     generated if not supplied)
1775
1776     @rtype: Section
1777     @return: Section object for the given section name
1778
1779     """
1780     privatetrans = False
1781     if session is None:
1782         session = DBConn().session()
1783         privatetrans = True
1784
1785     q = session.query(Section).filter_by(section=section)
1786     if q.count() == 0:
1787         ret = None
1788     else:
1789         ret = q.one()
1790
1791     if privatetrans:
1792         session.close()
1793
1794     return ret
1795
1796 __all__.append('get_section')
1797
1798 def get_sections(session=None):
1799     """
1800     Returns dictionary of section names -> id mappings
1801
1802     @type session: Session
1803     @param session: Optional SQL session object (a temporary one will be
1804     generated if not supplied)
1805
1806     @rtype: dictionary
1807     @return: dictionary of section names -> id mappings
1808     """
1809     privatetrans = False
1810     if session is None:
1811         session = DBConn().session()
1812         privatetrans = True
1813
1814     ret = {}
1815     q = session.query(Section)
1816     for x in q.all():
1817         ret[x.section] = x.section_id
1818
1819     if privatetrans:
1820         session.close()
1821
1822     return ret
1823
1824 __all__.append('get_sections')
1825
1826 ################################################################################
1827
1828 class DBSource(object):
1829     def __init__(self, *args, **kwargs):
1830         pass
1831
1832     def __repr__(self):
1833         return '<DBSource %s (%s)>' % (self.source, self.version)
1834
1835 __all__.append('DBSource')
1836
1837 def source_exists(source, source_version, suites = ["any"], session=None):
1838     """
1839     Ensure that source exists somewhere in the archive for the binary
1840     upload being processed.
1841       1. exact match     => 1.0-3
1842       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1843
1844     @type package: string
1845     @param package: package source name
1846
1847     @type source_version: string
1848     @param source_version: expected source version
1849
1850     @type suites: list
1851     @param suites: list of suites to check in, default I{any}
1852
1853     @type session: Session
1854     @param session: Optional SQLA session object (a temporary one will be
1855     generated if not supplied)
1856
1857     @rtype: int
1858     @return: returns 1 if a source with expected version is found, otherwise 0
1859
1860     """
1861
1862     privatetrans = False
1863     if session is None:
1864         session = DBConn().session()
1865         privatetrans = True
1866
1867     cnf = Config()
1868     ret = 1
1869
1870     for suite in suites:
1871         q = session.query(DBSource).filter_by(source=source)
1872         if suite != "any":
1873             # source must exist in suite X, or in some other suite that's
1874             # mapped to X, recursively... silent-maps are counted too,
1875             # unreleased-maps aren't.
1876             maps = cnf.ValueList("SuiteMappings")[:]
1877             maps.reverse()
1878             maps = [ m.split() for m in maps ]
1879             maps = [ (x[1], x[2]) for x in maps
1880                             if x[0] == "map" or x[0] == "silent-map" ]
1881             s = [suite]
1882             for x in maps:
1883                 if x[1] in s and x[0] not in s:
1884                     s.append(x[0])
1885
1886             q = q.join(SrcAssociation).join(Suite)
1887             q = q.filter(Suite.suite_name.in_(s))
1888
1889         # Reduce the query results to a list of version numbers
1890         ql = [ j.version for j in q.all() ]
1891
1892         # Try (1)
1893         if source_version in ql:
1894             continue
1895
1896         # Try (2)
1897         from daklib.regexes import re_bin_only_nmu
1898         orig_source_version = re_bin_only_nmu.sub('', source_version)
1899         if orig_source_version in ql:
1900             continue
1901
1902         # No source found so return not ok
1903         ret = 0
1904
1905     if privatetrans:
1906         session.close()
1907
1908     return ret
1909
1910 __all__.append('source_exists')
1911
1912 def get_suites_source_in(source, session=None):
1913     """
1914     Returns list of Suite objects which given C{source} name is in
1915
1916     @type source: str
1917     @param source: DBSource package name to search for
1918
1919     @rtype: list
1920     @return: list of Suite objects for the given source
1921     """
1922
1923     privatetrans = False
1924     if session is None:
1925         session = DBConn().session()
1926         privatetrans = True
1927
1928     ret = session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1929
1930     if privatetrans:
1931         session.close()
1932
1933     return ret
1934
1935 __all__.append('get_suites_source_in')
1936
1937 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1938     """
1939     Returns list of DBSource objects for given C{source} name and other parameters
1940
1941     @type source: str
1942     @param source: DBSource package name to search for
1943
1944     @type source: str or None
1945     @param source: DBSource version name to search for or None if not applicable
1946
1947     @type dm_upload_allowed: bool
1948     @param dm_upload_allowed: If None, no effect.  If True or False, only
1949     return packages with that dm_upload_allowed setting
1950
1951     @type session: Session
1952     @param session: Optional SQL session object (a temporary one will be
1953     generated if not supplied)
1954
1955     @rtype: list
1956     @return: list of DBSource objects for the given name (may be empty)
1957     """
1958     privatetrans = False
1959     if session is None:
1960         session = DBConn().session()
1961         privatetrans = True
1962
1963     q = session.query(DBSource).filter_by(source=source)
1964
1965     if version is not None:
1966         q = q.filter_by(version=version)
1967
1968     if dm_upload_allowed is not None:
1969         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1970
1971     ret = q.all()
1972
1973     if privatetrans:
1974         session.close()
1975
1976     return ret
1977
1978 __all__.append('get_sources_from_name')
1979
1980 def get_source_in_suite(source, suite, session=None):
1981     """
1982     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1983
1984       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1985       - B{suite} - a suite name, eg. I{unstable}
1986
1987     @type source: string
1988     @param source: source package name
1989
1990     @type suite: string
1991     @param suite: the suite name
1992
1993     @rtype: string
1994     @return: the version for I{source} in I{suite}
1995
1996     """
1997     privatetrans = False
1998     if session is None:
1999         session = DBConn().session()
2000         privatetrans = True
2001
2002     q = session.query(SrcAssociation)
2003     q = q.join('source').filter_by(source=source)
2004     q = q.join('suite').filter_by(suite_name=suite)
2005
2006     if q.count() == 0:
2007         ret =  None
2008     else:
2009         # ???: Maybe we should just return the SrcAssociation object instead
2010         ret = q.one().source
2011
2012     if privatetrans:
2013         session.close()
2014
2015     return ret
2016
2017 __all__.append('get_source_in_suite')
2018
2019 ################################################################################
2020
2021 class SrcAssociation(object):
2022     def __init__(self, *args, **kwargs):
2023         pass
2024
2025     def __repr__(self):
2026         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2027
2028 __all__.append('SrcAssociation')
2029
2030 ################################################################################
2031
2032 class SrcUploader(object):
2033     def __init__(self, *args, **kwargs):
2034         pass
2035
2036     def __repr__(self):
2037         return '<SrcUploader %s>' % self.uploader_id
2038
2039 __all__.append('SrcUploader')
2040
2041 ################################################################################
2042
2043 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2044                  ('SuiteID', 'suite_id'),
2045                  ('Version', 'version'),
2046                  ('Origin', 'origin'),
2047                  ('Label', 'label'),
2048                  ('Description', 'description'),
2049                  ('Untouchable', 'untouchable'),
2050                  ('Announce', 'announce'),
2051                  ('Codename', 'codename'),
2052                  ('OverrideCodename', 'overridecodename'),
2053                  ('ValidTime', 'validtime'),
2054                  ('Priority', 'priority'),
2055                  ('NotAutomatic', 'notautomatic'),
2056                  ('CopyChanges', 'copychanges'),
2057                  ('CopyDotDak', 'copydotdak'),
2058                  ('CommentsDir', 'commentsdir'),
2059                  ('OverrideSuite', 'overridesuite'),
2060                  ('ChangelogBase', 'changelogbase')]
2061
2062
2063 class Suite(object):
2064     def __init__(self, *args, **kwargs):
2065         pass
2066
2067     def __repr__(self):
2068         return '<Suite %s>' % self.suite_name
2069
2070     def __eq__(self, val):
2071         if isinstance(val, str):
2072             return (self.suite_name == val)
2073         # This signals to use the normal comparison operator
2074         return NotImplemented
2075
2076     def __ne__(self, val):
2077         if isinstance(val, str):
2078             return (self.suite_name != val)
2079         # This signals to use the normal comparison operator
2080         return NotImplemented
2081
2082     def details(self):
2083         ret = []
2084         for disp, field in SUITE_FIELDS:
2085             val = getattr(self, field, None)
2086             if val is not None:
2087                 ret.append("%s: %s" % (disp, val))
2088
2089         return "\n".join(ret)
2090
2091 __all__.append('Suite')
2092
2093 def get_suite_architecture(suite, architecture, session=None):
2094     """
2095     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2096     doesn't exist
2097
2098     @type suite: str
2099     @param suite: Suite name to search for
2100
2101     @type architecture: str
2102     @param architecture: Architecture name to search for
2103
2104     @type session: Session
2105     @param session: Optional SQL session object (a temporary one will be
2106     generated if not supplied)
2107
2108     @rtype: SuiteArchitecture
2109     @return: the SuiteArchitecture object or None
2110     """
2111
2112     privatetrans = False
2113     if session is None:
2114         session = DBConn().session()
2115         privatetrans = True
2116
2117     q = session.query(SuiteArchitecture)
2118     q = q.join(Architecture).filter_by(arch_string=architecture)
2119     q = q.join(Suite).filter_by(suite_name=suite)
2120
2121     if q.count() == 0:
2122         ret = None
2123     else:
2124         ret = q.one()
2125
2126     if privatetrans:
2127         session.close()
2128
2129     return ret
2130
2131 __all__.append('get_suite_architecture')
2132
2133 def get_suite(suite, session=None):
2134     """
2135     Returns Suite object for given C{suite name}.
2136
2137     @type suite: string
2138     @param suite: The name of the suite
2139
2140     @type session: Session
2141     @param session: Optional SQLA session object (a temporary one will be
2142     generated if not supplied)
2143
2144     @rtype: Suite
2145     @return: Suite object for the requested suite name (None if not presenT)
2146
2147     """
2148     privatetrans = False
2149     if session is None:
2150         session = DBConn().session()
2151         privatetrans = True
2152
2153     q = session.query(Suite).filter_by(suite_name=suite)
2154
2155     if q.count() == 0:
2156         ret = None
2157     else:
2158         ret = q.one()
2159
2160     if privatetrans:
2161         session.close()
2162
2163     return ret
2164
2165 __all__.append('get_suite')
2166
2167 ################################################################################
2168
2169 class SuiteArchitecture(object):
2170     def __init__(self, *args, **kwargs):
2171         pass
2172
2173     def __repr__(self):
2174         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2175
2176 __all__.append('SuiteArchitecture')
2177
2178 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2179     """
2180     Returns list of Architecture objects for given C{suite} name
2181
2182     @type source: str
2183     @param source: Suite name to search for
2184
2185     @type skipsrc: boolean
2186     @param skipsrc: Whether to skip returning the 'source' architecture entry
2187     (Default False)
2188
2189     @type skipall: boolean
2190     @param skipall: Whether to skip returning the 'all' architecture entry
2191     (Default False)
2192
2193     @type session: Session
2194     @param session: Optional SQL session object (a temporary one will be
2195     generated if not supplied)
2196
2197     @rtype: list
2198     @return: list of Architecture objects for the given name (may be empty)
2199     """
2200
2201     privatetrans = False
2202     if session is None:
2203         session = DBConn().session()
2204         privatetrans = True
2205
2206     q = session.query(Architecture)
2207     q = q.join(SuiteArchitecture)
2208     q = q.join(Suite).filter_by(suite_name=suite)
2209
2210     if skipsrc:
2211         q = q.filter(Architecture.arch_string != 'source')
2212
2213     if skipall:
2214         q = q.filter(Architecture.arch_string != 'all')
2215
2216     q = q.order_by('arch_string')
2217
2218     ret = q.all()
2219
2220     if privatetrans:
2221         session.close()
2222
2223     return ret
2224
2225 __all__.append('get_suite_architectures')
2226
2227 ################################################################################
2228
2229 class Uid(object):
2230     def __init__(self, *args, **kwargs):
2231         pass
2232
2233     def __eq__(self, val):
2234         if isinstance(val, str):
2235             return (self.uid == val)
2236         # This signals to use the normal comparison operator
2237         return NotImplemented
2238
2239     def __ne__(self, val):
2240         if isinstance(val, str):
2241             return (self.uid != val)
2242         # This signals to use the normal comparison operator
2243         return NotImplemented
2244
2245     def __repr__(self):
2246         return '<Uid %s (%s)>' % (self.uid, self.name)
2247
2248 __all__.append('Uid')
2249
2250 def add_database_user(uidname, session=None):
2251     """
2252     Adds a database user
2253
2254     @type uidname: string
2255     @param uidname: The uid of the user to add
2256
2257     @type session: SQLAlchemy
2258     @param session: Optional SQL session object (a temporary one will be
2259     generated if not supplied).  If not passed, a commit will be performed at
2260     the end of the function, otherwise the caller is responsible for commiting.
2261
2262     @rtype: Uid
2263     @return: the uid object for the given uidname
2264     """
2265
2266     privatetrans = False
2267     if session is None:
2268         session = DBConn().session()
2269         privatetrans = True
2270
2271     session.execute("CREATE USER :uid", {'uid': uidname})
2272
2273     if privatetrans:
2274         session.commit()
2275         session.close()
2276
2277 __all__.append('add_database_user')
2278
2279 def get_or_set_uid(uidname, session=None):
2280     """
2281     Returns uid object for given uidname.
2282
2283     If no matching uidname is found, a row is inserted.
2284
2285     @type uidname: string
2286     @param uidname: The uid to add
2287
2288     @type session: SQLAlchemy
2289     @param session: Optional SQL session object (a temporary one will be
2290     generated if not supplied).  If not passed, a commit will be performed at
2291     the end of the function, otherwise the caller is responsible for commiting.
2292
2293     @rtype: Uid
2294     @return: the uid object for the given uidname
2295     """
2296
2297     privatetrans = False
2298     if session is None:
2299         session = DBConn().session()
2300         privatetrans = True
2301
2302     q = session.query(Uid).filter_by(uid=uidname)
2303
2304     if q.count() < 1:
2305         uid = Uid()
2306         uid.uid = uidname
2307         session.add(uid)
2308         if privatetrans:
2309             session.commit()
2310         else:
2311             session.flush()
2312         ret = uid
2313     else:
2314         ret = q.one()
2315
2316     if privatetrans:
2317         session.close()
2318
2319     return ret
2320
2321 __all__.append('get_or_set_uid')
2322
2323
2324 def get_uid_from_fingerprint(fpr, session=None):
2325     privatetrans = False
2326     if session is None:
2327         session = DBConn().session()
2328         privatetrans = True
2329
2330     q = session.query(Uid)
2331     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2332
2333     if q.count() != 1:
2334         ret = None
2335     else:
2336         ret = q.one()
2337
2338     if privatetrans:
2339         session.close()
2340
2341     return ret
2342
2343 __all__.append('get_uid_from_fingerprint')
2344
2345 ################################################################################
2346
2347 class DBConn(Singleton):
2348     """
2349     database module init.
2350     """
2351     def __init__(self, *args, **kwargs):
2352         super(DBConn, self).__init__(*args, **kwargs)
2353
2354     def _startup(self, *args, **kwargs):
2355         self.debug = False
2356         if kwargs.has_key('debug'):
2357             self.debug = True
2358         self.__createconn()
2359
2360     def __setuptables(self):
2361         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2362         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2363         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2364         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2365         self.tbl_component = Table('component', self.db_meta, autoload=True)
2366         self.tbl_config = Table('config', self.db_meta, autoload=True)
2367         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2368         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2369         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2370         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2371         self.tbl_files = Table('files', self.db_meta, autoload=True)
2372         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2373         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2374         self.tbl_location = Table('location', self.db_meta, autoload=True)
2375         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2376         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2377         self.tbl_override = Table('override', self.db_meta, autoload=True)
2378         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2379         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2380         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2381         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2382         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2383         self.tbl_section = Table('section', self.db_meta, autoload=True)
2384         self.tbl_source = Table('source', self.db_meta, autoload=True)
2385         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2386         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2387         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2388         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2389         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2390
2391     def __setupmappers(self):
2392         mapper(Architecture, self.tbl_architecture,
2393                properties = dict(arch_id = self.tbl_architecture.c.id))
2394
2395         mapper(Archive, self.tbl_archive,
2396                properties = dict(archive_id = self.tbl_archive.c.id,
2397                                  archive_name = self.tbl_archive.c.name))
2398
2399         mapper(BinAssociation, self.tbl_bin_associations,
2400                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2401                                  suite_id = self.tbl_bin_associations.c.suite,
2402                                  suite = relation(Suite),
2403                                  binary_id = self.tbl_bin_associations.c.bin,
2404                                  binary = relation(DBBinary)))
2405
2406         mapper(DBBinary, self.tbl_binaries,
2407                properties = dict(binary_id = self.tbl_binaries.c.id,
2408                                  package = self.tbl_binaries.c.package,
2409                                  version = self.tbl_binaries.c.version,
2410                                  maintainer_id = self.tbl_binaries.c.maintainer,
2411                                  maintainer = relation(Maintainer),
2412                                  source_id = self.tbl_binaries.c.source,
2413                                  source = relation(DBSource),
2414                                  arch_id = self.tbl_binaries.c.architecture,
2415                                  architecture = relation(Architecture),
2416                                  poolfile_id = self.tbl_binaries.c.file,
2417                                  poolfile = relation(PoolFile),
2418                                  binarytype = self.tbl_binaries.c.type,
2419                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2420                                  fingerprint = relation(Fingerprint),
2421                                  install_date = self.tbl_binaries.c.install_date,
2422                                  binassociations = relation(BinAssociation,
2423                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2424
2425         mapper(Component, self.tbl_component,
2426                properties = dict(component_id = self.tbl_component.c.id,
2427                                  component_name = self.tbl_component.c.name))
2428
2429         mapper(DBConfig, self.tbl_config,
2430                properties = dict(config_id = self.tbl_config.c.id))
2431
2432         mapper(ContentAssociation, self.tbl_content_associations,
2433                properties = dict(ca_id = self.tbl_content_associations.c.id,
2434                                  filename_id = self.tbl_content_associations.c.filename,
2435                                  filename    = relation(ContentFilename),
2436                                  filepath_id = self.tbl_content_associations.c.filepath,
2437                                  filepath    = relation(ContentFilepath),
2438                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
2439                                  binary      = relation(DBBinary)))
2440
2441
2442         mapper(ContentFilename, self.tbl_content_file_names,
2443                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2444                                  filename = self.tbl_content_file_names.c.file))
2445
2446         mapper(ContentFilepath, self.tbl_content_file_paths,
2447                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2448                                  filepath = self.tbl_content_file_paths.c.path))
2449
2450         mapper(DSCFile, self.tbl_dsc_files,
2451                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2452                                  source_id = self.tbl_dsc_files.c.source,
2453                                  source = relation(DBSource),
2454                                  poolfile_id = self.tbl_dsc_files.c.file,
2455                                  poolfile = relation(PoolFile)))
2456
2457         mapper(PoolFile, self.tbl_files,
2458                properties = dict(file_id = self.tbl_files.c.id,
2459                                  filesize = self.tbl_files.c.size,
2460                                  location_id = self.tbl_files.c.location,
2461                                  location = relation(Location)))
2462
2463         mapper(Fingerprint, self.tbl_fingerprint,
2464                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2465                                  uid_id = self.tbl_fingerprint.c.uid,
2466                                  uid = relation(Uid),
2467                                  keyring_id = self.tbl_fingerprint.c.keyring,
2468                                  keyring = relation(Keyring)))
2469
2470         mapper(Keyring, self.tbl_keyrings,
2471                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2472                                  keyring_id = self.tbl_keyrings.c.id))
2473
2474         mapper(Location, self.tbl_location,
2475                properties = dict(location_id = self.tbl_location.c.id,
2476                                  component_id = self.tbl_location.c.component,
2477                                  component = relation(Component),
2478                                  archive_id = self.tbl_location.c.archive,
2479                                  archive = relation(Archive),
2480                                  archive_type = self.tbl_location.c.type))
2481
2482         mapper(Maintainer, self.tbl_maintainer,
2483                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2484
2485         mapper(NewComment, self.tbl_new_comments,
2486                properties = dict(comment_id = self.tbl_new_comments.c.id))
2487
2488         mapper(Override, self.tbl_override,
2489                properties = dict(suite_id = self.tbl_override.c.suite,
2490                                  suite = relation(Suite),
2491                                  component_id = self.tbl_override.c.component,
2492                                  component = relation(Component),
2493                                  priority_id = self.tbl_override.c.priority,
2494                                  priority = relation(Priority),
2495                                  section_id = self.tbl_override.c.section,
2496                                  section = relation(Section),
2497                                  overridetype_id = self.tbl_override.c.type,
2498                                  overridetype = relation(OverrideType)))
2499
2500         mapper(OverrideType, self.tbl_override_type,
2501                properties = dict(overridetype = self.tbl_override_type.c.type,
2502                                  overridetype_id = self.tbl_override_type.c.id))
2503
2504         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2505                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2506                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
2507                                  filepath = relation(ContentFilepath),
2508                                  filename_id = self.tbl_pending_content_associations.c.filename,
2509                                  filename = relation(ContentFilename)))
2510
2511         mapper(Priority, self.tbl_priority,
2512                properties = dict(priority_id = self.tbl_priority.c.id))
2513
2514         mapper(Queue, self.tbl_queue,
2515                properties = dict(queue_id = self.tbl_queue.c.id))
2516
2517         mapper(QueueBuild, self.tbl_queue_build,
2518                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2519                                  queue_id = self.tbl_queue_build.c.queue,
2520                                  queue = relation(Queue, backref='queuebuild')))
2521
2522         mapper(Section, self.tbl_section,
2523                properties = dict(section_id = self.tbl_section.c.id))
2524
2525         mapper(DBSource, self.tbl_source,
2526                properties = dict(source_id = self.tbl_source.c.id,
2527                                  version = self.tbl_source.c.version,
2528                                  maintainer_id = self.tbl_source.c.maintainer,
2529                                  maintainer = relation(Maintainer,
2530                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2531                                  poolfile_id = self.tbl_source.c.file,
2532                                  poolfile = relation(PoolFile),
2533                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2534                                  fingerprint = relation(Fingerprint),
2535                                  changedby_id = self.tbl_source.c.changedby,
2536                                  changedby = relation(Maintainer,
2537                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2538                                  srcfiles = relation(DSCFile,
2539                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2540                                  srcassociations = relation(SrcAssociation,
2541                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2542
2543         mapper(SrcAssociation, self.tbl_src_associations,
2544                properties = dict(sa_id = self.tbl_src_associations.c.id,
2545                                  suite_id = self.tbl_src_associations.c.suite,
2546                                  suite = relation(Suite),
2547                                  source_id = self.tbl_src_associations.c.source,
2548                                  source = relation(DBSource)))
2549
2550         mapper(SrcUploader, self.tbl_src_uploaders,
2551                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2552                                  source_id = self.tbl_src_uploaders.c.source,
2553                                  source = relation(DBSource,
2554                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2555                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2556                                  maintainer = relation(Maintainer,
2557                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2558
2559         mapper(Suite, self.tbl_suite,
2560                properties = dict(suite_id = self.tbl_suite.c.id))
2561
2562         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2563                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2564                                  suite = relation(Suite, backref='suitearchitectures'),
2565                                  arch_id = self.tbl_suite_architectures.c.architecture,
2566                                  architecture = relation(Architecture)))
2567
2568         mapper(Uid, self.tbl_uid,
2569                properties = dict(uid_id = self.tbl_uid.c.id,
2570                                  fingerprint = relation(Fingerprint)))
2571
2572     ## Connection functions
2573     def __createconn(self):
2574         from config import Config
2575         cnf = Config()
2576         if cnf["DB::Host"]:
2577             # TCP/IP
2578             connstr = "postgres://%s" % cnf["DB::Host"]
2579             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2580                 connstr += ":%s" % cnf["DB::Port"]
2581             connstr += "/%s" % cnf["DB::Name"]
2582         else:
2583             # Unix Socket
2584             connstr = "postgres:///%s" % cnf["DB::Name"]
2585             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2586                 connstr += "?port=%s" % cnf["DB::Port"]
2587
2588         self.db_pg   = create_engine(connstr, echo=self.debug)
2589         self.db_meta = MetaData()
2590         self.db_meta.bind = self.db_pg
2591         self.db_smaker = sessionmaker(bind=self.db_pg,
2592                                       autoflush=True,
2593                                       autocommit=False)
2594
2595         self.__setuptables()
2596         self.__setupmappers()
2597
2598     def session(self):
2599         return self.db_smaker()
2600
2601 __all__.append('DBConn')
2602
2603