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