]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
convert add_user 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).  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 class Suite(object):
955     def __init__(self, *args, **kwargs):
956         pass
957
958     def __repr__(self):
959         return '<Suite %s>' % self.suite_name
960
961 __all__.append('Suite')
962
963 def get_suite_architecture(suite, architecture, session=None):
964     """
965     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
966     doesn't exist
967
968     @type suite: str
969     @param suite: Suite name to search for
970
971     @type architecture: str
972     @param architecture: Architecture name to search for
973
974     @type session: Session
975     @param session: Optional SQL session object (a temporary one will be
976     generated if not supplied)
977
978     @rtype: SuiteArchitecture
979     @return: the SuiteArchitecture object or None
980     """
981
982     if session is None:
983         session = DBConn().session()
984
985     q = session.query(SuiteArchitecture)
986     q = q.join(Architecture).filter_by(arch_string=architecture)
987     q = q.join(Suite).filter_by(suite_name=suite)
988     if q.count() == 0:
989         return None
990     return q.one()
991
992 __all__.append('get_suite_architecture')
993
994 def get_suite(suite, session=None):
995     """
996     Returns Suite object for given C{suite name}.
997
998     @type suite: string
999     @param suite: The name of the suite
1000
1001     @type session: Session
1002     @param session: Optional SQLA session object (a temporary one will be
1003     generated if not supplied)
1004
1005     @rtype: Suite
1006     @return: Suite object for the requested suite name (None if not presenT)
1007
1008     """
1009     if session is None:
1010         session = DBConn().session()
1011     q = session.query(Suite).filter_by(suite_name=suite)
1012     if q.count() == 0:
1013         return None
1014     return q.one()
1015
1016 __all__.append('get_suite')
1017
1018 ################################################################################
1019
1020 class SuiteArchitecture(object):
1021     def __init__(self, *args, **kwargs):
1022         pass
1023
1024     def __repr__(self):
1025         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1026
1027 __all__.append('SuiteArchitecture')
1028
1029 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1030     """
1031     Returns list of Architecture objects for given C{suite} name
1032
1033     @type source: str
1034     @param source: Suite name to search for
1035
1036     @type skipsrc: boolean
1037     @param skipsrc: Whether to skip returning the 'source' architecture entry
1038     (Default False)
1039
1040     @type skipall: boolean
1041     @param skipall: Whether to skip returning the 'all' architecture entry
1042     (Default False)
1043
1044     @type session: Session
1045     @param session: Optional SQL session object (a temporary one will be
1046     generated if not supplied)
1047
1048     @rtype: list
1049     @return: list of Architecture objects for the given name (may be empty)
1050     """
1051
1052     if session is None:
1053         session = DBConn().session()
1054
1055     q = session.query(Architecture)
1056     q = q.join(SuiteArchitecture)
1057     q = q.join(Suite).filter_by(suite_name=suite)
1058     if skipsrc:
1059         q = q.filter(Architecture.arch_string != 'source')
1060     if skipall:
1061         q = q.filter(Architecture.arch_string != 'all')
1062     q = q.order_by('arch_string')
1063     return q.all()
1064
1065 __all__.append('get_suite_architectures')
1066
1067 ################################################################################
1068
1069 class Uid(object):
1070     def __init__(self, *args, **kwargs):
1071         pass
1072
1073     def __repr__(self):
1074         return '<Uid %s (%s)>' % (self.uid, self.name)
1075
1076 __all__.append('Uid')
1077
1078 def add_database_user(uidname, session=None):
1079     """
1080     Adds a database user
1081
1082     @type uidname: string
1083     @param uidname: The uid of the user to add
1084
1085     @type session: SQLAlchemy
1086     @param session: Optional SQL session object (a temporary one will be
1087     generated if not supplied).  If not passed, a commit will be performed at
1088     the end of the function, otherwise the caller is responsible for commiting.
1089
1090     @rtype: Uid
1091     @return: the uid object for the given uidname
1092     """
1093     privatetrans = False
1094     if session is None:
1095         session = DBConn().session()
1096         privatetrans = True
1097
1098     try:
1099         session.execute("CREATE USER :uid", {'uid': uidname})
1100         if privatetrans:
1101             session.commit()
1102     except:
1103         traceback.print_exc()
1104         raise
1105
1106 __all__.append('add_database_user')
1107
1108 def get_or_set_uid(uidname, session=None):
1109     """
1110     Returns uid object for given uidname.
1111
1112     If no matching uidname is found, a row is inserted.
1113
1114     @type uidname: string
1115     @param uidname: The uid to add
1116
1117     @type session: SQLAlchemy
1118     @param session: Optional SQL session object (a temporary one will be
1119     generated if not supplied).  If not passed, a commit will be performed at
1120     the end of the function, otherwise the caller is responsible for commiting.
1121
1122     @rtype: Uid
1123     @return: the uid object for the given uidname
1124     """
1125     privatetrans = False
1126     if session is None:
1127         session = DBConn().session()
1128         privatetrans = True
1129
1130     try:
1131         q = session.query(Uid).filter_by(uid=uidname)
1132         if q.count() < 1:
1133             uid = Uid()
1134             uid.uid = uidname
1135             session.add(uid)
1136             if privatetrans:
1137                 session.commit()
1138             return uid
1139         else:
1140             return q.one()
1141
1142     except:
1143         traceback.print_exc()
1144         raise
1145
1146 __all__.append('get_or_set_uid')
1147
1148
1149 def get_uid_from_fingerprint(fpr, session=None):
1150     if session is None:
1151         session = DBConn().session()
1152
1153     q = session.query(Uid)
1154     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
1155
1156     if q.count() != 1:
1157         return None
1158     else:
1159         return q.one()
1160
1161 __all__.append('get_uid_from_fingerprint')
1162
1163 ################################################################################
1164
1165 class DBConn(Singleton):
1166     """
1167     database module init.
1168     """
1169     def __init__(self, *args, **kwargs):
1170         super(DBConn, self).__init__(*args, **kwargs)
1171
1172     def _startup(self, *args, **kwargs):
1173         self.debug = False
1174         if kwargs.has_key('debug'):
1175             self.debug = True
1176         self.__createconn()
1177
1178     def __setuptables(self):
1179         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
1180         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
1181         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
1182         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
1183         self.tbl_component = Table('component', self.db_meta, autoload=True)
1184         self.tbl_config = Table('config', self.db_meta, autoload=True)
1185         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
1186         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
1187         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
1188         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
1189         self.tbl_files = Table('files', self.db_meta, autoload=True)
1190         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
1191         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
1192         self.tbl_location = Table('location', self.db_meta, autoload=True)
1193         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
1194         self.tbl_override = Table('override', self.db_meta, autoload=True)
1195         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
1196         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
1197         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
1198         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
1199         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
1200         self.tbl_section = Table('section', self.db_meta, autoload=True)
1201         self.tbl_source = Table('source', self.db_meta, autoload=True)
1202         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
1203         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
1204         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
1205         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
1206         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
1207
1208     def __setupmappers(self):
1209         mapper(Architecture, self.tbl_architecture,
1210                properties = dict(arch_id = self.tbl_architecture.c.id))
1211
1212         mapper(Archive, self.tbl_archive,
1213                properties = dict(archive_id = self.tbl_archive.c.id,
1214                                  archive_name = self.tbl_archive.c.name))
1215
1216         mapper(BinAssociation, self.tbl_bin_associations,
1217                properties = dict(ba_id = self.tbl_bin_associations.c.id,
1218                                  suite_id = self.tbl_bin_associations.c.suite,
1219                                  suite = relation(Suite),
1220                                  binary_id = self.tbl_bin_associations.c.bin,
1221                                  binary = relation(DBBinary)))
1222
1223         mapper(DBBinary, self.tbl_binaries,
1224                properties = dict(binary_id = self.tbl_binaries.c.id,
1225                                  package = self.tbl_binaries.c.package,
1226                                  version = self.tbl_binaries.c.version,
1227                                  maintainer_id = self.tbl_binaries.c.maintainer,
1228                                  maintainer = relation(Maintainer),
1229                                  source_id = self.tbl_binaries.c.source,
1230                                  source = relation(DBSource),
1231                                  arch_id = self.tbl_binaries.c.architecture,
1232                                  architecture = relation(Architecture),
1233                                  poolfile_id = self.tbl_binaries.c.file,
1234                                  poolfile = relation(PoolFile),
1235                                  binarytype = self.tbl_binaries.c.type,
1236                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
1237                                  fingerprint = relation(Fingerprint),
1238                                  install_date = self.tbl_binaries.c.install_date,
1239                                  binassociations = relation(BinAssociation,
1240                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
1241
1242         mapper(Component, self.tbl_component,
1243                properties = dict(component_id = self.tbl_component.c.id,
1244                                  component_name = self.tbl_component.c.name))
1245
1246         mapper(DBConfig, self.tbl_config,
1247                properties = dict(config_id = self.tbl_config.c.id))
1248
1249         mapper(ContentAssociation, self.tbl_content_associations,
1250                properties = dict(ca_id = self.tbl_content_associations.c.id,
1251                                  filename_id = self.tbl_content_associations.c.filename,
1252                                  filename    = relation(ContentFilename),
1253                                  filepath_id = self.tbl_content_associations.c.filepath,
1254                                  filepath    = relation(ContentFilepath),
1255                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
1256                                  binary      = relation(DBBinary)))
1257
1258
1259         mapper(ContentFilename, self.tbl_content_file_names,
1260                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
1261                                  filename = self.tbl_content_file_names.c.file))
1262
1263         mapper(ContentFilepath, self.tbl_content_file_paths,
1264                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
1265                                  filepath = self.tbl_content_file_paths.c.path))
1266
1267         mapper(DSCFile, self.tbl_dsc_files,
1268                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
1269                                  source_id = self.tbl_dsc_files.c.source,
1270                                  source = relation(DBSource),
1271                                  poolfile_id = self.tbl_dsc_files.c.file,
1272                                  poolfile = relation(PoolFile)))
1273
1274         mapper(PoolFile, self.tbl_files,
1275                properties = dict(file_id = self.tbl_files.c.id,
1276                                  filesize = self.tbl_files.c.size,
1277                                  location_id = self.tbl_files.c.location,
1278                                  location = relation(Location)))
1279
1280         mapper(Fingerprint, self.tbl_fingerprint,
1281                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
1282                                  uid_id = self.tbl_fingerprint.c.uid,
1283                                  uid = relation(Uid),
1284                                  keyring_id = self.tbl_fingerprint.c.keyring,
1285                                  keyring = relation(Keyring)))
1286
1287         mapper(Keyring, self.tbl_keyrings,
1288                properties = dict(keyring_name = self.tbl_keyrings.c.name,
1289                                  keyring_id = self.tbl_keyrings.c.id))
1290
1291         mapper(Location, self.tbl_location,
1292                properties = dict(location_id = self.tbl_location.c.id,
1293                                  component_id = self.tbl_location.c.component,
1294                                  component = relation(Component),
1295                                  archive_id = self.tbl_location.c.archive,
1296                                  archive = relation(Archive),
1297                                  archive_type = self.tbl_location.c.type))
1298
1299         mapper(Maintainer, self.tbl_maintainer,
1300                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
1301
1302         mapper(Override, self.tbl_override,
1303                properties = dict(suite_id = self.tbl_override.c.suite,
1304                                  suite = relation(Suite),
1305                                  component_id = self.tbl_override.c.component,
1306                                  component = relation(Component),
1307                                  priority_id = self.tbl_override.c.priority,
1308                                  priority = relation(Priority),
1309                                  section_id = self.tbl_override.c.section,
1310                                  section = relation(Section),
1311                                  overridetype_id = self.tbl_override.c.type,
1312                                  overridetype = relation(OverrideType)))
1313
1314         mapper(OverrideType, self.tbl_override_type,
1315                properties = dict(overridetype = self.tbl_override_type.c.type,
1316                                  overridetype_id = self.tbl_override_type.c.id))
1317
1318         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
1319                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
1320                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
1321                                  filepath = relation(ContentFilepath),
1322                                  filename_id = self.tbl_pending_content_associations.c.filename,
1323                                  filename = relation(ContentFilename)))
1324
1325         mapper(Priority, self.tbl_priority,
1326                properties = dict(priority_id = self.tbl_priority.c.id))
1327
1328         mapper(Queue, self.tbl_queue,
1329                properties = dict(queue_id = self.tbl_queue.c.id))
1330
1331         mapper(QueueBuild, self.tbl_queue_build,
1332                properties = dict(suite_id = self.tbl_queue_build.c.suite,
1333                                  queue_id = self.tbl_queue_build.c.queue,
1334                                  queue = relation(Queue)))
1335
1336         mapper(Section, self.tbl_section,
1337                properties = dict(section_id = self.tbl_section.c.id))
1338
1339         mapper(DBSource, self.tbl_source,
1340                properties = dict(source_id = self.tbl_source.c.id,
1341                                  version = self.tbl_source.c.version,
1342                                  maintainer_id = self.tbl_source.c.maintainer,
1343                                  maintainer = relation(Maintainer,
1344                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
1345                                  poolfile_id = self.tbl_source.c.file,
1346                                  poolfile = relation(PoolFile),
1347                                  fingerprint_id = self.tbl_source.c.sig_fpr,
1348                                  fingerprint = relation(Fingerprint),
1349                                  changedby_id = self.tbl_source.c.changedby,
1350                                  changedby = relation(Maintainer,
1351                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
1352                                  srcfiles = relation(DSCFile,
1353                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
1354                                  srcassociations = relation(SrcAssociation,
1355                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
1356
1357         mapper(SrcAssociation, self.tbl_src_associations,
1358                properties = dict(sa_id = self.tbl_src_associations.c.id,
1359                                  suite_id = self.tbl_src_associations.c.suite,
1360                                  suite = relation(Suite),
1361                                  source_id = self.tbl_src_associations.c.source,
1362                                  source = relation(DBSource)))
1363
1364         mapper(SrcUploader, self.tbl_src_uploaders,
1365                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
1366                                  source_id = self.tbl_src_uploaders.c.source,
1367                                  source = relation(DBSource,
1368                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
1369                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
1370                                  maintainer = relation(Maintainer,
1371                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
1372
1373         mapper(Suite, self.tbl_suite,
1374                properties = dict(suite_id = self.tbl_suite.c.id))
1375
1376         mapper(SuiteArchitecture, self.tbl_suite_architectures,
1377                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
1378                                  suite = relation(Suite, backref='suitearchitectures'),
1379                                  arch_id = self.tbl_suite_architectures.c.architecture,
1380                                  architecture = relation(Architecture)))
1381
1382         mapper(Uid, self.tbl_uid,
1383                properties = dict(uid_id = self.tbl_uid.c.id,
1384                                  fingerprint = relation(Fingerprint)))
1385
1386     ## Connection functions
1387     def __createconn(self):
1388         from config import Config
1389         cnf = Config()
1390         if cnf["DB::Host"]:
1391             # TCP/IP
1392             connstr = "postgres://%s" % cnf["DB::Host"]
1393             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1394                 connstr += ":%s" % cnf["DB::Port"]
1395             connstr += "/%s" % cnf["DB::Name"]
1396         else:
1397             # Unix Socket
1398             connstr = "postgres:///%s" % cnf["DB::Name"]
1399             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1400                 connstr += "?port=%s" % cnf["DB::Port"]
1401
1402         self.db_pg   = create_engine(connstr, echo=self.debug)
1403         self.db_meta = MetaData()
1404         self.db_meta.bind = self.db_pg
1405         self.db_smaker = sessionmaker(bind=self.db_pg,
1406                                       autoflush=True,
1407                                       autocommit=False)
1408
1409         self.__setuptables()
1410         self.__setupmappers()
1411
1412     def session(self):
1413         return self.db_smaker()
1414
1415 __all__.append('DBConn')
1416