]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
allow querying on dm_upload_allowed
[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 from singleton import Singleton
47
48 ################################################################################
49
50 __all__ = ['IntegrityError', 'SQLAlchemyError']
51
52 ################################################################################
53
54 class Architecture(object):
55     def __init__(self, *args, **kwargs):
56         pass
57
58     def __repr__(self):
59         return '<Architecture %s>' % self.arch_string
60
61 __all__.append('Architecture')
62
63 def get_architecture(architecture, session=None):
64     """
65     Returns database id for given C{architecture}.
66
67     @type architecture: string
68     @param architecture: The name of the architecture
69
70     @type session: Session
71     @param session: Optional SQLA session object (a temporary one will be
72     generated if not supplied)
73
74     @rtype: Architecture
75     @return: Architecture object for the given arch (None if not present)
76
77     """
78     if session is None:
79         session = DBConn().session()
80     q = session.query(Architecture).filter_by(arch_string=architecture)
81     if q.count() == 0:
82         return None
83     return q.one()
84
85 __all__.append('get_architecture')
86
87 def get_architecture_suites(architecture, session=None):
88     """
89     Returns list of Suite objects for given C{architecture} name
90
91     @type source: str
92     @param source: Architecture name to search for
93
94     @type session: Session
95     @param session: Optional SQL session object (a temporary one will be
96     generated if not supplied)
97
98     @rtype: list
99     @return: list of Suite objects for the given name (may be empty)
100     """
101
102     if session is None:
103         session = DBConn().session()
104
105     q = session.query(Suite)
106     q = q.join(SuiteArchitecture)
107     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
108     return q.all()
109
110 __all__.append('get_architecture_suites')
111
112 ################################################################################
113
114 class Archive(object):
115     def __init__(self, *args, **kwargs):
116         pass
117
118     def __repr__(self):
119         return '<Archive %s>' % self.name
120
121 __all__.append('Archive')
122
123 def get_archive(archive, session=None):
124     """
125     returns database id for given c{archive}.
126
127     @type archive: string
128     @param archive: the name of the arhive
129
130     @type session: Session
131     @param session: Optional SQLA session object (a temporary one will be
132     generated if not supplied)
133
134     @rtype: Archive
135     @return: Archive object for the given name (None if not present)
136
137     """
138     archive = archive.lower()
139     if session is None:
140         session = DBConn().session()
141     q = session.query(Archive).filter_by(archive_name=archive)
142     if q.count() == 0:
143         return None
144     return q.one()
145
146 __all__.append('get_archive')
147
148 ################################################################################
149
150 class BinAssociation(object):
151     def __init__(self, *args, **kwargs):
152         pass
153
154     def __repr__(self):
155         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
156
157 __all__.append('BinAssociation')
158
159 ################################################################################
160
161 class DBBinary(object):
162     def __init__(self, *args, **kwargs):
163         pass
164
165     def __repr__(self):
166         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
167
168 __all__.append('DBBinary')
169
170 def get_binary_from_id(id, session=None):
171     """
172     Returns DBBinary object for given C{id}
173
174     @type id: int
175     @param id: Id of the required binary
176
177     @type session: Session
178     @param session: Optional SQLA session object (a temporary one will be
179     generated if not supplied)
180
181     @rtype: DBBinary
182     @return: DBBinary object for the given binary (None if not present)
183     """
184     if session is None:
185         session = DBConn().session()
186     q = session.query(DBBinary).filter_by(binary_id=id)
187     if q.count() == 0:
188         return None
189     return q.one()
190
191 __all__.append('get_binary_from_id')
192
193 def get_binaries_from_name(package, session=None):
194     """
195     Returns list of DBBinary objects for given C{package} name
196
197     @type package: str
198     @param package: DBBinary package name to search for
199
200     @type session: Session
201     @param session: Optional SQL session object (a temporary one will be
202     generated if not supplied)
203
204     @rtype: list
205     @return: list of DBBinary objects for the given name (may be empty)
206     """
207     if session is None:
208         session = DBConn().session()
209     return session.query(DBBinary).filter_by(package=package).all()
210
211 __all__.append('get_binaries_from_name')
212
213 def get_binary_components(package, suitename, arch, session=None):
214 # Check for packages that have moved from one component to another
215     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
216     WHERE b.package=:package AND s.suite_name=:suitename
217       AND (a.arch_string = :arch OR a.arch_string = 'all')
218       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
219       AND f.location = l.id
220       AND l.component = c.id
221       AND b.file = f.id"""
222
223     vals = {'package': package, 'suitename': suitename, 'arch': arch}
224
225     if session is None:
226         session = DBConn().session()
227     return session.execute(query, vals)
228
229 __all__.append('get_binary_components')
230 ################################################################################
231
232 class Component(object):
233     def __init__(self, *args, **kwargs):
234         pass
235
236     def __repr__(self):
237         return '<Component %s>' % self.component_name
238
239
240 __all__.append('Component')
241
242 def get_component(component, session=None):
243     """
244     Returns database id for given C{component}.
245
246     @type component: string
247     @param component: The name of the override type
248
249     @rtype: int
250     @return: the database id for the given component
251
252     """
253     component = component.lower()
254     if session is None:
255         session = DBConn().session()
256     q = session.query(Component).filter_by(component_name=component)
257     if q.count() == 0:
258         return None
259     return q.one()
260
261 __all__.append('get_component')
262
263 ################################################################################
264
265 class DBConfig(object):
266     def __init__(self, *args, **kwargs):
267         pass
268
269     def __repr__(self):
270         return '<DBConfig %s>' % self.name
271
272 __all__.append('DBConfig')
273
274 ################################################################################
275
276 class ContentFilename(object):
277     def __init__(self, *args, **kwargs):
278         pass
279
280     def __repr__(self):
281         return '<ContentFilename %s>' % self.filename
282
283 __all__.append('ContentFilename')
284
285 def get_or_set_contents_file_id(filename, session=None):
286     """
287     Returns database id for given filename.
288
289     If no matching file is found, a row is inserted.
290
291     @type filename: string
292     @param filename: The filename
293     @type session: SQLAlchemy
294     @param session: Optional SQL session object (a temporary one will be
295     generated if not supplied)
296
297     @rtype: int
298     @return: the database id for the given component
299     """
300     if session is None:
301         session = DBConn().session()
302
303     try:
304         q = session.query(ContentFilename).filter_by(filename=filename)
305         if q.count() < 1:
306             cf = ContentFilename()
307             cf.filename = filename
308             session.add(cf)
309             return cf.cafilename_id
310         else:
311             return q.one().cafilename_id
312
313     except:
314         traceback.print_exc()
315         raise
316
317 __all__.append('get_or_set_contents_file_id')
318
319 ################################################################################
320
321 class ContentFilepath(object):
322     def __init__(self, *args, **kwargs):
323         pass
324
325     def __repr__(self):
326         return '<ContentFilepath %s>' % self.filepath
327
328 __all__.append('ContentFilepath')
329
330 def get_or_set_contents_path_id(filepath, session):
331     """
332     Returns database id for given path.
333
334     If no matching file is found, a row is inserted.
335
336     @type filename: string
337     @param filename: The filepath
338     @type session: SQLAlchemy
339     @param session: Optional SQL session object (a temporary one will be
340     generated if not supplied)
341
342     @rtype: int
343     @return: the database id for the given path
344     """
345     if session is None:
346         session = DBConn().session()
347
348     try:
349         q = session.query(ContentFilepath).filter_by(filepath=filepath)
350         if q.count() < 1:
351             cf = ContentFilepath()
352             cf.filepath = filepath
353             session.add(cf)
354             return cf.cafilepath_id
355         else:
356             return q.one().cafilepath_id
357
358     except:
359         traceback.print_exc()
360         raise
361
362 __all__.append('get_or_set_contents_path_id')
363
364 ################################################################################
365
366 class ContentAssociation(object):
367     def __init__(self, *args, **kwargs):
368         pass
369
370     def __repr__(self):
371         return '<ContentAssociation %s>' % self.ca_id
372
373 __all__.append('ContentAssociation')
374
375 def insert_content_paths(binary_id, fullpaths, session=None):
376     """
377     Make sure given path is associated with given binary id
378
379     @type binary_id: int
380     @param binary_id: the id of the binary
381     @type fullpaths: list
382     @param fullpaths: the list of paths of the file being associated with the binary
383     @type session: SQLAlchemy session
384     @param session: Optional SQLAlchemy session.  If this is passed, the caller
385     is responsible for ensuring a transaction has begun and committing the
386     results or rolling back based on the result code.  If not passed, a commit
387     will be performed at the end of the function
388
389     @return: True upon success
390     """
391
392     privatetrans = False
393
394     if session is None:
395         session = DBConn().session()
396         privatetrans = True
397
398     try:
399         for fullpath in fullpaths:
400             (path, file) = os.path.split(fullpath)
401
402             # Get the necessary IDs ...
403             ca = ContentAssociation()
404             ca.binary_id = binary_id
405             ca.filename_id = get_or_set_contents_file_id(file)
406             ca.filepath_id = get_or_set_contents_path_id(path)
407             session.add(ca)
408
409         # Only commit if we set up the session ourself
410         if privatetrans:
411             session.commit()
412
413         return True
414     except:
415         traceback.print_exc()
416
417         # Only rollback if we set up the session ourself
418         if privatetrans:
419             session.rollback()
420
421         return False
422
423 __all__.append('insert_content_paths')
424
425 ################################################################################
426
427 class DSCFile(object):
428     def __init__(self, *args, **kwargs):
429         pass
430
431     def __repr__(self):
432         return '<DSCFile %s>' % self.dscfile_id
433
434 __all__.append('DSCFile')
435
436 ################################################################################
437
438 class PoolFile(object):
439     def __init__(self, *args, **kwargs):
440         pass
441
442     def __repr__(self):
443         return '<PoolFile %s>' % self.filename
444
445 __all__.append('PoolFile')
446
447 def get_poolfile_by_name(filename, location_id=None, session=None):
448     """
449     Returns an array of PoolFile objects for the given filename and
450     (optionally) location_id
451
452     @type filename: string
453     @param filename: the filename of the file to check against the DB
454
455     @type location_id: int
456     @param location_id: the id of the location to look in (optional)
457
458     @rtype: array
459     @return: array of PoolFile objects
460     """
461
462     if session is not None:
463         session = DBConn().session()
464
465     q = session.query(PoolFile).filter_by(filename=filename)
466
467     if location_id is not None:
468         q = q.join(Location).filter_by(location_id=location_id)
469
470     return q.all()
471
472 __all__.append('get_poolfile_by_name')
473
474 ################################################################################
475
476 class Fingerprint(object):
477     def __init__(self, *args, **kwargs):
478         pass
479
480     def __repr__(self):
481         return '<Fingerprint %s>' % self.fingerprint
482
483 __all__.append('Fingerprint')
484
485 ################################################################################
486
487 class Keyring(object):
488     def __init__(self, *args, **kwargs):
489         pass
490
491     def __repr__(self):
492         return '<Keyring %s>' % self.keyring_name
493
494 __all__.append('Keyring')
495
496 ################################################################################
497
498 class Location(object):
499     def __init__(self, *args, **kwargs):
500         pass
501
502     def __repr__(self):
503         return '<Location %s (%s)>' % (self.path, self.location_id)
504
505 __all__.append('Location')
506
507 def get_location(location, component=None, archive=None, session=None):
508     """
509     Returns Location object for the given combination of location, component
510     and archive
511
512     @type location: string
513     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
514
515     @type component: string
516     @param component: the component name (if None, no restriction applied)
517
518     @type archive: string
519     @param archive_id: the archive name (if None, no restriction applied)
520
521     @rtype: Location / None
522     @return: Either a Location object or None if one can't be found
523     """
524
525     if session is None:
526         session = DBConn().session()
527
528     q = session.query(Location).filter_by(path=location)
529
530     if archive is not None:
531         q = q.join(Archive).filter_by(archive_name=archive)
532
533     if component is not None:
534         q = q.join(Component).filter_by(component_name=component)
535
536     if q.count() < 1:
537         return None
538     else:
539         return q.one()
540
541 __all__.append('get_location')
542
543 ################################################################################
544
545 class Maintainer(object):
546     def __init__(self, *args, **kwargs):
547         pass
548
549     def __repr__(self):
550         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
551
552 __all__.append('Maintainer')
553
554 ################################################################################
555
556 class Override(object):
557     def __init__(self, *args, **kwargs):
558         pass
559
560     def __repr__(self):
561         return '<Override %s (%s)>' % (self.package, self.suite_id)
562
563 __all__.append('Override')
564
565 ################################################################################
566
567 class OverrideType(object):
568     def __init__(self, *args, **kwargs):
569         pass
570
571     def __repr__(self):
572         return '<OverrideType %s>' % self.overridetype
573
574 __all__.append('OverrideType')
575
576 def get_override_type(override_type, session=None):
577     """
578     Returns OverrideType object for given C{override type}.
579
580     @type override_type: string
581     @param override_type: The name of the override type
582
583     @type session: Session
584     @param session: Optional SQLA session object (a temporary one will be
585     generated if not supplied)
586
587     @rtype: int
588     @return: the database id for the given override type
589
590     """
591     if session is None:
592         session = DBConn().session()
593     q = session.query(Priority).filter_by(priority=priority)
594     if q.count() == 0:
595         return None
596     return q.one()
597
598 __all__.append('get_override_type')
599
600 ################################################################################
601
602 class PendingContentAssociation(object):
603     def __init__(self, *args, **kwargs):
604         pass
605
606     def __repr__(self):
607         return '<PendingContentAssociation %s>' % self.pca_id
608
609 __all__.append('PendingContentAssociation')
610
611 def insert_pending_content_paths(package, fullpaths, session=None):
612     """
613     Make sure given paths are temporarily associated with given
614     package
615
616     @type package: dict
617     @param package: the package to associate with should have been read in from the binary control file
618     @type fullpaths: list
619     @param fullpaths: the list of paths of the file being associated with the binary
620     @type session: SQLAlchemy session
621     @param session: Optional SQLAlchemy session.  If this is passed, the caller
622     is responsible for ensuring a transaction has begun and committing the
623     results or rolling back based on the result code.  If not passed, a commit
624     will be performed at the end of the function
625
626     @return: True upon success, False if there is a problem
627     """
628
629     privatetrans = False
630
631     if session is None:
632         session = DBConn().session()
633         privatetrans = True
634
635     try:
636         arch = get_architecture(package['Architecture'], session)
637         arch_id = arch.arch_id
638
639         # Remove any already existing recorded files for this package
640         q = session.query(PendingContentAssociation)
641         q = q.filter_by(package=package['Package'])
642         q = q.filter_by(version=package['Version'])
643         q = q.filter_by(architecture=arch_id)
644         q.delete()
645
646         # Insert paths
647         for fullpath in fullpaths:
648             (path, file) = os.path.split(fullpath)
649
650             if path.startswith( "./" ):
651                 path = path[2:]
652
653             pca = PendingContentAssociation()
654             pca.package = package['Package']
655             pca.version = package['Version']
656             pca.filename_id = get_or_set_contents_file_id(file, session)
657             pca.filepath_id = get_or_set_contents_path_id(path, session)
658             pca.architecture = arch_id
659             session.add(pca)
660
661         # Only commit if we set up the session ourself
662         if privatetrans:
663             session.commit()
664
665         return True
666     except:
667         traceback.print_exc()
668
669         # Only rollback if we set up the session ourself
670         if privatetrans:
671             session.rollback()
672
673         return False
674
675 __all__.append('insert_pending_content_paths')
676
677 ################################################################################
678
679 class Priority(object):
680     def __init__(self, *args, **kwargs):
681         pass
682
683     def __repr__(self):
684         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
685
686 __all__.append('Priority')
687
688 def get_priority(priority, session=None):
689     """
690     Returns Priority object for given C{priority name}.
691
692     @type priority: string
693     @param priority: The name of the priority
694
695     @type session: Session
696     @param session: Optional SQLA session object (a temporary one will be
697     generated if not supplied)
698
699     @rtype: Priority
700     @return: Priority object for the given priority
701
702     """
703     if session is None:
704         session = DBConn().session()
705     q = session.query(Priority).filter_by(priority=priority)
706     if q.count() == 0:
707         return None
708     return q.one()
709
710 __all__.append('get_priority')
711
712 ################################################################################
713
714 class Queue(object):
715     def __init__(self, *args, **kwargs):
716         pass
717
718     def __repr__(self):
719         return '<Queue %s>' % self.queue_name
720
721 __all__.append('Queue')
722
723 ################################################################################
724
725 class QueueBuild(object):
726     def __init__(self, *args, **kwargs):
727         pass
728
729     def __repr__(self):
730         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
731
732 __all__.append('QueueBuild')
733
734 ################################################################################
735
736 class Section(object):
737     def __init__(self, *args, **kwargs):
738         pass
739
740     def __repr__(self):
741         return '<Section %s>' % self.section
742
743 __all__.append('Section')
744
745 def get_section(section, session=None):
746     """
747     Returns Section object for given C{section name}.
748
749     @type section: string
750     @param section: The name of the section
751
752     @type session: Session
753     @param session: Optional SQLA session object (a temporary one will be
754     generated if not supplied)
755
756     @rtype: Section
757     @return: Section object for the given section name
758
759     """
760     if session is None:
761         session = DBConn().session()
762     q = session.query(Section).filter_by(section=section)
763     if q.count() == 0:
764         return None
765     return q.one()
766
767 __all__.append('get_section')
768
769 ################################################################################
770
771 class DBSource(object):
772     def __init__(self, *args, **kwargs):
773         pass
774
775     def __repr__(self):
776         return '<DBSource %s (%s)>' % (self.source, self.version)
777
778 __all__.append('DBSource')
779
780 def get_sources_from_name(source, dm_upload_allowed=None, session=None):
781     """
782     Returns list of DBSource objects for given C{source} name
783
784     @type source: str
785     @param source: DBSource package name to search for
786
787     @type dm_upload_allowed: bool
788     @param dm_upload_allowed: If None, no effect.  If True or False, only
789     return packages with that dm_upload_allowed setting
790
791     @type session: Session
792     @param session: Optional SQL session object (a temporary one will be
793     generated if not supplied)
794
795     @rtype: list
796     @return: list of DBSource objects for the given name (may be empty)
797     """
798     if session is None:
799         session = DBConn().session()
800
801     q = session.query(DBSource).filter_by(source=source)
802     if dm_upload_allowed is not None:
803         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
804
805     return q.all()
806
807 __all__.append('get_sources_from_name')
808
809 def get_source_in_suite(source, suite, session=None):
810     """
811     Returns list of DBSource objects for a combination of C{source} and C{suite}.
812
813       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
814       - B{suite} - a suite name, eg. I{unstable}
815
816     @type source: string
817     @param source: source package name
818
819     @type suite: string
820     @param suite: the suite name
821
822     @rtype: string
823     @return: the version for I{source} in I{suite}
824
825     """
826     if session is None:
827         session = DBConn().session()
828     q = session.query(SrcAssociation)
829     q = q.join('source').filter_by(source=source)
830     q = q.join('suite').filter_by(suite_name=suite)
831     if q.count() == 0:
832         return None
833     # ???: Maybe we should just return the SrcAssociation object instead
834     return q.one().source
835
836 __all__.append('get_source_in_suite')
837
838 ################################################################################
839
840 class SrcAssociation(object):
841     def __init__(self, *args, **kwargs):
842         pass
843
844     def __repr__(self):
845         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
846
847 __all__.append('SrcAssociation')
848
849 ################################################################################
850
851 class SrcUploader(object):
852     def __init__(self, *args, **kwargs):
853         pass
854
855     def __repr__(self):
856         return '<SrcUploader %s>' % self.uploader_id
857
858 __all__.append('SrcUploader')
859
860 ################################################################################
861
862 class Suite(object):
863     def __init__(self, *args, **kwargs):
864         pass
865
866     def __repr__(self):
867         return '<Suite %s>' % self.suite_name
868
869 __all__.append('Suite')
870
871 def get_suite_architecture(suite, architecture, session=None):
872     """
873     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
874     doesn't exist
875
876     @type suite: str
877     @param suite: Suite name to search for
878
879     @type architecture: str
880     @param architecture: Architecture name to search for
881
882     @type session: Session
883     @param session: Optional SQL session object (a temporary one will be
884     generated if not supplied)
885
886     @rtype: SuiteArchitecture
887     @return: the SuiteArchitecture object or None
888     """
889
890     if session is None:
891         session = DBConn().session()
892
893     q = session.query(SuiteArchitecture)
894     q = q.join(Architecture).filter_by(arch_string=architecture)
895     q = q.join(Suite).filter_by(suite_name=suite)
896     if q.count() == 0:
897         return None
898     return q.one()
899
900 __all__.append('get_suite_architecture')
901
902 def get_suite(suite, session=None):
903     """
904     Returns Suite object for given C{suite name}.
905
906     @type suite: string
907     @param suite: The name of the suite
908
909     @type session: Session
910     @param session: Optional SQLA session object (a temporary one will be
911     generated if not supplied)
912
913     @rtype: Suite
914     @return: Suite object for the requested suite name (None if not presenT)
915
916     """
917     if session is None:
918         session = DBConn().session()
919     q = session.query(Suite).filter_by(suite_name=suite)
920     if q.count() == 0:
921         return None
922     return q.one()
923
924 __all__.append('get_suite')
925
926 ################################################################################
927
928 class SuiteArchitecture(object):
929     def __init__(self, *args, **kwargs):
930         pass
931
932     def __repr__(self):
933         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
934
935 __all__.append('SuiteArchitecture')
936
937 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
938     """
939     Returns list of Architecture objects for given C{suite} name
940
941     @type source: str
942     @param source: Suite name to search for
943
944     @type skipsrc: boolean
945     @param skipsrc: Whether to skip returning the 'source' architecture entry
946     (Default False)
947
948     @type skipall: boolean
949     @param skipall: Whether to skip returning the 'all' architecture entry
950     (Default False)
951
952     @type session: Session
953     @param session: Optional SQL session object (a temporary one will be
954     generated if not supplied)
955
956     @rtype: list
957     @return: list of Architecture objects for the given name (may be empty)
958     """
959
960     if session is None:
961         session = DBConn().session()
962
963     q = session.query(Architecture)
964     q = q.join(SuiteArchitecture)
965     q = q.join(Suite).filter_by(suite_name=suite)
966     if skipsrc:
967         q = q.filter(Architecture.arch_string != 'source')
968     if skipall:
969         q = q.filter(Architecture.arch_string != 'all')
970     q = q.order_by('arch_string')
971     return q.all()
972
973 __all__.append('get_suite_architectures')
974
975 ################################################################################
976
977 class Uid(object):
978     def __init__(self, *args, **kwargs):
979         pass
980
981     def __repr__(self):
982         return '<Uid %s (%s)>' % (self.uid, self.name)
983
984 __all__.append('Uid')
985
986 def get_uid_from_fingerprint(fpr, session=None):
987     if session is None:
988         session = DBConn().session()
989
990     q = session.query(Uid)
991     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
992
993     if q.count() != 1:
994         return None
995     else:
996         return q.one()
997
998 __all__.append('get_uid_from_fingerprint')
999
1000 ################################################################################
1001
1002 class DBConn(Singleton):
1003     """
1004     database module init.
1005     """
1006     def __init__(self, *args, **kwargs):
1007         super(DBConn, self).__init__(*args, **kwargs)
1008
1009     def _startup(self, *args, **kwargs):
1010         self.debug = False
1011         if kwargs.has_key('debug'):
1012             self.debug = True
1013         self.__createconn()
1014
1015     def __setuptables(self):
1016         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
1017         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
1018         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
1019         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
1020         self.tbl_component = Table('component', self.db_meta, autoload=True)
1021         self.tbl_config = Table('config', self.db_meta, autoload=True)
1022         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
1023         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
1024         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
1025         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
1026         self.tbl_files = Table('files', self.db_meta, autoload=True)
1027         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
1028         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
1029         self.tbl_location = Table('location', self.db_meta, autoload=True)
1030         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
1031         self.tbl_override = Table('override', self.db_meta, autoload=True)
1032         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
1033         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
1034         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
1035         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
1036         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
1037         self.tbl_section = Table('section', self.db_meta, autoload=True)
1038         self.tbl_source = Table('source', self.db_meta, autoload=True)
1039         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
1040         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
1041         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
1042         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
1043         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
1044
1045     def __setupmappers(self):
1046         mapper(Architecture, self.tbl_architecture,
1047                properties = dict(arch_id = self.tbl_architecture.c.id))
1048
1049         mapper(Archive, self.tbl_archive,
1050                properties = dict(archive_id = self.tbl_archive.c.id,
1051                                  archive_name = self.tbl_archive.c.name))
1052
1053         mapper(BinAssociation, self.tbl_bin_associations,
1054                properties = dict(ba_id = self.tbl_bin_associations.c.id,
1055                                  suite_id = self.tbl_bin_associations.c.suite,
1056                                  suite = relation(Suite),
1057                                  binary_id = self.tbl_bin_associations.c.bin,
1058                                  binary = relation(DBBinary)))
1059
1060         mapper(DBBinary, self.tbl_binaries,
1061                properties = dict(binary_id = self.tbl_binaries.c.id,
1062                                  package = self.tbl_binaries.c.package,
1063                                  version = self.tbl_binaries.c.version,
1064                                  maintainer_id = self.tbl_binaries.c.maintainer,
1065                                  maintainer = relation(Maintainer),
1066                                  source_id = self.tbl_binaries.c.source,
1067                                  source = relation(DBSource),
1068                                  arch_id = self.tbl_binaries.c.architecture,
1069                                  architecture = relation(Architecture),
1070                                  poolfile_id = self.tbl_binaries.c.file,
1071                                  poolfile = relation(PoolFile),
1072                                  binarytype = self.tbl_binaries.c.type,
1073                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
1074                                  fingerprint = relation(Fingerprint),
1075                                  install_date = self.tbl_binaries.c.install_date,
1076                                  binassociations = relation(BinAssociation,
1077                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
1078
1079         mapper(Component, self.tbl_component,
1080                properties = dict(component_id = self.tbl_component.c.id,
1081                                  component_name = self.tbl_component.c.name))
1082
1083         mapper(DBConfig, self.tbl_config,
1084                properties = dict(config_id = self.tbl_config.c.id))
1085
1086         mapper(ContentAssociation, self.tbl_content_associations,
1087                properties = dict(ca_id = self.tbl_content_associations.c.id,
1088                                  filename_id = self.tbl_content_associations.c.filename,
1089                                  filename    = relation(ContentFilename),
1090                                  filepath_id = self.tbl_content_associations.c.filepath,
1091                                  filepath    = relation(ContentFilepath),
1092                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
1093                                  binary      = relation(DBBinary)))
1094
1095
1096         mapper(ContentFilename, self.tbl_content_file_names,
1097                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
1098                                  filename = self.tbl_content_file_names.c.file))
1099
1100         mapper(ContentFilepath, self.tbl_content_file_paths,
1101                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
1102                                  filepath = self.tbl_content_file_paths.c.path))
1103
1104         mapper(DSCFile, self.tbl_dsc_files,
1105                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
1106                                  source_id = self.tbl_dsc_files.c.source,
1107                                  source = relation(DBSource),
1108                                  poolfile_id = self.tbl_dsc_files.c.file,
1109                                  poolfile = relation(PoolFile)))
1110
1111         mapper(PoolFile, self.tbl_files,
1112                properties = dict(file_id = self.tbl_files.c.id,
1113                                  filesize = self.tbl_files.c.size,
1114                                  location_id = self.tbl_files.c.location,
1115                                  location = relation(Location)))
1116
1117         mapper(Fingerprint, self.tbl_fingerprint,
1118                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
1119                                  uid_id = self.tbl_fingerprint.c.uid,
1120                                  uid = relation(Uid),
1121                                  keyring_id = self.tbl_fingerprint.c.keyring,
1122                                  keyring = relation(Keyring)))
1123
1124         mapper(Keyring, self.tbl_keyrings,
1125                properties = dict(keyring_name = self.tbl_keyrings.c.name,
1126                                  keyring_id = self.tbl_keyrings.c.id))
1127
1128         mapper(Location, self.tbl_location,
1129                properties = dict(location_id = self.tbl_location.c.id,
1130                                  component_id = self.tbl_location.c.component,
1131                                  component = relation(Component),
1132                                  archive_id = self.tbl_location.c.archive,
1133                                  archive = relation(Archive),
1134                                  archive_type = self.tbl_location.c.type))
1135
1136         mapper(Maintainer, self.tbl_maintainer,
1137                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
1138
1139         mapper(Override, self.tbl_override,
1140                properties = dict(suite_id = self.tbl_override.c.suite,
1141                                  suite = relation(Suite),
1142                                  component_id = self.tbl_override.c.component,
1143                                  component = relation(Component),
1144                                  priority_id = self.tbl_override.c.priority,
1145                                  priority = relation(Priority),
1146                                  section_id = self.tbl_override.c.section,
1147                                  section = relation(Section),
1148                                  overridetype_id = self.tbl_override.c.type,
1149                                  overridetype = relation(OverrideType)))
1150
1151         mapper(OverrideType, self.tbl_override_type,
1152                properties = dict(overridetype = self.tbl_override_type.c.type,
1153                                  overridetype_id = self.tbl_override_type.c.id))
1154
1155         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
1156                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
1157                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
1158                                  filepath = relation(ContentFilepath),
1159                                  filename_id = self.tbl_pending_content_associations.c.filename,
1160                                  filename = relation(ContentFilename)))
1161
1162         mapper(Priority, self.tbl_priority,
1163                properties = dict(priority_id = self.tbl_priority.c.id))
1164
1165         mapper(Queue, self.tbl_queue,
1166                properties = dict(queue_id = self.tbl_queue.c.id))
1167
1168         mapper(QueueBuild, self.tbl_queue_build,
1169                properties = dict(suite_id = self.tbl_queue_build.c.suite,
1170                                  queue_id = self.tbl_queue_build.c.queue,
1171                                  queue = relation(Queue)))
1172
1173         mapper(Section, self.tbl_section,
1174                properties = dict(section_id = self.tbl_section.c.id))
1175
1176         mapper(DBSource, self.tbl_source,
1177                properties = dict(source_id = self.tbl_source.c.id,
1178                                  version = self.tbl_source.c.version,
1179                                  maintainer_id = self.tbl_source.c.maintainer,
1180                                  maintainer = relation(Maintainer,
1181                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
1182                                  poolfile_id = self.tbl_source.c.file,
1183                                  poolfile = relation(PoolFile),
1184                                  fingerprint_id = self.tbl_source.c.sig_fpr,
1185                                  fingerprint = relation(Fingerprint),
1186                                  changedby_id = self.tbl_source.c.changedby,
1187                                  changedby = relation(Maintainer,
1188                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
1189                                  srcfiles = relation(DSCFile,
1190                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
1191                                  srcassociations = relation(SrcAssociation,
1192                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
1193
1194         mapper(SrcAssociation, self.tbl_src_associations,
1195                properties = dict(sa_id = self.tbl_src_associations.c.id,
1196                                  suite_id = self.tbl_src_associations.c.suite,
1197                                  suite = relation(Suite),
1198                                  source_id = self.tbl_src_associations.c.source,
1199                                  source = relation(DBSource)))
1200
1201         mapper(SrcUploader, self.tbl_src_uploaders,
1202                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
1203                                  source_id = self.tbl_src_uploaders.c.source,
1204                                  source = relation(DBSource,
1205                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
1206                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
1207                                  maintainer = relation(Maintainer,
1208                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
1209
1210         mapper(Suite, self.tbl_suite,
1211                properties = dict(suite_id = self.tbl_suite.c.id))
1212
1213         mapper(SuiteArchitecture, self.tbl_suite_architectures,
1214                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
1215                                  suite = relation(Suite, backref='suitearchitectures'),
1216                                  arch_id = self.tbl_suite_architectures.c.architecture,
1217                                  architecture = relation(Architecture)))
1218
1219         mapper(Uid, self.tbl_uid,
1220                properties = dict(uid_id = self.tbl_uid.c.id,
1221                                  fingerprint = relation(Fingerprint)))
1222
1223     ## Connection functions
1224     def __createconn(self):
1225         from config import Config
1226         cnf = Config()
1227         if cnf["DB::Host"]:
1228             # TCP/IP
1229             connstr = "postgres://%s" % cnf["DB::Host"]
1230             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1231                 connstr += ":%s" % cnf["DB::Port"]
1232             connstr += "/%s" % cnf["DB::Name"]
1233         else:
1234             # Unix Socket
1235             connstr = "postgres:///%s" % cnf["DB::Name"]
1236             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1237                 connstr += "?port=%s" % cnf["DB::Port"]
1238
1239         self.db_pg   = create_engine(connstr, echo=self.debug)
1240         self.db_meta = MetaData()
1241         self.db_meta.bind = self.db_pg
1242         self.db_smaker = sessionmaker(bind=self.db_pg,
1243                                       autoflush=True,
1244                                       autocommit=False)
1245
1246         self.__setuptables()
1247         self.__setupmappers()
1248
1249     def session(self):
1250         return self.db_smaker()
1251
1252 __all__.append('DBConn')
1253