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