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