]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
Fix Archive.__repr__
[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 inspect import getargspec
41
42 from sqlalchemy import create_engine, Table, MetaData, select
43 from sqlalchemy.orm import sessionmaker, mapper, relation
44
45 # Don't remove this, we re-export the exceptions to scripts which import us
46 from sqlalchemy.exc import *
47
48 # Only import Config until Queue stuff is changed to store its config
49 # in the database
50 from config import Config
51 from singleton import Singleton
52 from textutils import fix_maintainer
53
54 ################################################################################
55
56 __all__ = ['IntegrityError', 'SQLAlchemyError']
57
58 ################################################################################
59
60 def session_wrapper(fn):
61     def wrapped(*args, **kwargs):
62         private_transaction = False
63         session = kwargs.get('session')
64
65         # No session specified as last argument or in kwargs, create one.
66         if session is None and len(args) == len(getargspec(fn)[0]) - 1:
67             private_transaction = True
68             kwargs['session'] = DBConn().session()
69
70         try:
71             return fn(*args, **kwargs)
72         finally:
73             if private_transaction:
74                 # We created a session; close it.
75                 kwargs['session'].close()
76
77     return wrapped
78
79 ################################################################################
80
81 class Architecture(object):
82     def __init__(self, *args, **kwargs):
83         pass
84
85     def __eq__(self, val):
86         if isinstance(val, str):
87             return (self.arch_string== val)
88         # This signals to use the normal comparison operator
89         return NotImplemented
90
91     def __ne__(self, val):
92         if isinstance(val, str):
93             return (self.arch_string != val)
94         # This signals to use the normal comparison operator
95         return NotImplemented
96
97     def __repr__(self):
98         return '<Architecture %s>' % self.arch_string
99
100 __all__.append('Architecture')
101
102 @session_wrapper
103 def get_architecture(architecture, session=None):
104     """
105     Returns database id for given C{architecture}.
106
107     @type architecture: string
108     @param architecture: The name of the architecture
109
110     @type session: Session
111     @param session: Optional SQLA session object (a temporary one will be
112     generated if not supplied)
113
114     @rtype: Architecture
115     @return: Architecture object for the given arch (None if not present)
116     """
117
118     q = session.query(Architecture).filter_by(arch_string=architecture)
119
120     if q.count() == 0:
121         ret = None
122     else:
123         ret = q.one()
124
125     return ret
126
127 __all__.append('get_architecture')
128
129 @session_wrapper
130 def get_architecture_suites(architecture, session=None):
131     """
132     Returns list of Suite objects for given C{architecture} name
133
134     @type source: str
135     @param source: Architecture name to search for
136
137     @type session: Session
138     @param session: Optional SQL session object (a temporary one will be
139     generated if not supplied)
140
141     @rtype: list
142     @return: list of Suite objects for the given name (may be empty)
143     """
144
145     q = session.query(Suite)
146     q = q.join(SuiteArchitecture)
147     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
148
149     ret = q.all()
150
151     return ret
152
153 __all__.append('get_architecture_suites')
154
155 ################################################################################
156
157 class Archive(object):
158     def __init__(self, *args, **kwargs):
159         pass
160
161     def __repr__(self):
162         return '<Archive %s>' % self.archive_name
163
164 __all__.append('Archive')
165
166 @session_wrapper
167 def get_archive(archive, session=None):
168     """
169     returns database id for given c{archive}.
170
171     @type archive: string
172     @param archive: the name of the arhive
173
174     @type session: Session
175     @param session: Optional SQLA session object (a temporary one will be
176     generated if not supplied)
177
178     @rtype: Archive
179     @return: Archive object for the given name (None if not present)
180
181     """
182     archive = archive.lower()
183
184     q = session.query(Archive).filter_by(archive_name=archive)
185
186     if q.count() == 0:
187         ret = None
188     else:
189         ret = q.one()
190
191     return ret
192
193 __all__.append('get_archive')
194
195 ################################################################################
196
197 class BinAssociation(object):
198     def __init__(self, *args, **kwargs):
199         pass
200
201     def __repr__(self):
202         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
203
204 __all__.append('BinAssociation')
205
206 ################################################################################
207
208 class DBBinary(object):
209     def __init__(self, *args, **kwargs):
210         pass
211
212     def __repr__(self):
213         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
214
215 __all__.append('DBBinary')
216
217 @session_wrapper
218 def get_suites_binary_in(package, session=None):
219     """
220     Returns list of Suite objects which given C{package} name is in
221
222     @type source: str
223     @param source: DBBinary package name to search for
224
225     @rtype: list
226     @return: list of Suite objects for the given package
227     """
228
229     ret = session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
230
231     return ret
232
233 __all__.append('get_suites_binary_in')
234
235 @session_wrapper
236 def get_binary_from_id(id, session=None):
237     """
238     Returns DBBinary object for given C{id}
239
240     @type id: int
241     @param id: Id of the required binary
242
243     @type session: Session
244     @param session: Optional SQLA session object (a temporary one will be
245     generated if not supplied)
246
247     @rtype: DBBinary
248     @return: DBBinary object for the given binary (None if not present)
249     """
250
251     q = session.query(DBBinary).filter_by(binary_id=id)
252
253     if q.count() == 0:
254         ret = None
255     else:
256         ret = q.one()
257
258     return ret
259
260 __all__.append('get_binary_from_id')
261
262 @session_wrapper
263 def get_binaries_from_name(package, version=None, architecture=None, session=None):
264     """
265     Returns list of DBBinary objects for given C{package} name
266
267     @type package: str
268     @param package: DBBinary package name to search for
269
270     @type version: str or None
271     @param version: Version to search for (or None)
272
273     @type package: str, list or None
274     @param package: Architectures to limit to (or None if no limit)
275
276     @type session: Session
277     @param session: Optional SQL session object (a temporary one will be
278     generated if not supplied)
279
280     @rtype: list
281     @return: list of DBBinary objects for the given name (may be empty)
282     """
283
284     q = session.query(DBBinary).filter_by(package=package)
285
286     if version is not None:
287         q = q.filter_by(version=version)
288
289     if architecture is not None:
290         if not isinstance(architecture, list):
291             architecture = [architecture]
292         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
293
294     ret = q.all()
295
296     return ret
297
298 __all__.append('get_binaries_from_name')
299
300 @session_wrapper
301 def get_binaries_from_source_id(source_id, session=None):
302     """
303     Returns list of DBBinary objects for given C{source_id}
304
305     @type source_id: int
306     @param source_id: source_id to search for
307
308     @type session: Session
309     @param session: Optional SQL session object (a temporary one will be
310     generated if not supplied)
311
312     @rtype: list
313     @return: list of DBBinary objects for the given name (may be empty)
314     """
315
316     ret = session.query(DBBinary).filter_by(source_id=source_id).all()
317
318     return ret
319
320 __all__.append('get_binaries_from_source_id')
321
322 @session_wrapper
323 def get_binary_from_name_suite(package, suitename, session=None):
324     ### For dak examine-package
325     ### XXX: Doesn't use object API yet
326
327     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
328              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
329              WHERE b.package=:package
330                AND b.file = fi.id
331                AND fi.location = l.id
332                AND l.component = c.id
333                AND ba.bin=b.id
334                AND ba.suite = su.id
335                AND su.suite_name=:suitename
336           ORDER BY b.version DESC"""
337
338     ret = session.execute(sql, {'package': package, 'suitename': suitename})
339
340     return ret
341
342 __all__.append('get_binary_from_name_suite')
343
344 @session_wrapper
345 def get_binary_components(package, suitename, arch, session=None):
346     # Check for packages that have moved from one component to another
347     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
348     WHERE b.package=:package AND s.suite_name=:suitename
349       AND (a.arch_string = :arch OR a.arch_string = 'all')
350       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
351       AND f.location = l.id
352       AND l.component = c.id
353       AND b.file = f.id"""
354
355     vals = {'package': package, 'suitename': suitename, 'arch': arch}
356
357     ret = session.execute(query, vals)
358
359     return ret
360
361 __all__.append('get_binary_components')
362
363 ################################################################################
364
365 class Component(object):
366     def __init__(self, *args, **kwargs):
367         pass
368
369     def __eq__(self, val):
370         if isinstance(val, str):
371             return (self.component_name == val)
372         # This signals to use the normal comparison operator
373         return NotImplemented
374
375     def __ne__(self, val):
376         if isinstance(val, str):
377             return (self.component_name != val)
378         # This signals to use the normal comparison operator
379         return NotImplemented
380
381     def __repr__(self):
382         return '<Component %s>' % self.component_name
383
384
385 __all__.append('Component')
386
387 @session_wrapper
388 def get_component(component, session=None):
389     """
390     Returns database id for given C{component}.
391
392     @type component: string
393     @param component: The name of the override type
394
395     @rtype: int
396     @return: the database id for the given component
397
398     """
399     component = component.lower()
400
401     q = session.query(Component).filter_by(component_name=component)
402
403     if q.count() == 0:
404         ret = None
405     else:
406         ret = q.one()
407
408     return ret
409
410 __all__.append('get_component')
411
412 ################################################################################
413
414 class DBConfig(object):
415     def __init__(self, *args, **kwargs):
416         pass
417
418     def __repr__(self):
419         return '<DBConfig %s>' % self.name
420
421 __all__.append('DBConfig')
422
423 ################################################################################
424
425 class ContentFilename(object):
426     def __init__(self, *args, **kwargs):
427         pass
428
429     def __repr__(self):
430         return '<ContentFilename %s>' % self.filename
431
432 __all__.append('ContentFilename')
433
434 def get_or_set_contents_file_id(filename, session=None):
435     """
436     Returns database id for given filename.
437
438     If no matching file is found, a row is inserted.
439
440     @type filename: string
441     @param filename: The filename
442     @type session: SQLAlchemy
443     @param session: Optional SQL session object (a temporary one will be
444     generated if not supplied).  If not passed, a commit will be performed at
445     the end of the function, otherwise the caller is responsible for commiting.
446
447     @rtype: int
448     @return: the database id for the given component
449     """
450     privatetrans = False
451     if session is None:
452         session = DBConn().session()
453         privatetrans = True
454
455     q = session.query(ContentFilename).filter_by(filename=filename)
456     if q.count() < 1:
457         cf = ContentFilename()
458         cf.filename = filename
459         session.add(cf)
460         if privatetrans:
461             session.commit()
462         else:
463             session.flush()
464         ret = cf.cafilename_id
465     else:
466         ret = q.one().cafilename_id
467
468     if privatetrans:
469         session.close()
470
471     return ret
472
473 __all__.append('get_or_set_contents_file_id')
474
475 @session_wrapper
476 def get_contents(suite, overridetype, section=None, session=None):
477     """
478     Returns contents for a suite / overridetype combination, limiting
479     to a section if not None.
480
481     @type suite: Suite
482     @param suite: Suite object
483
484     @type overridetype: OverrideType
485     @param overridetype: OverrideType object
486
487     @type section: Section
488     @param section: Optional section object to limit results to
489
490     @type session: SQLAlchemy
491     @param session: Optional SQL session object (a temporary one will be
492     generated if not supplied)
493
494     @rtype: ResultsProxy
495     @return: ResultsProxy object set up to return tuples of (filename, section,
496     package, arch_id)
497     """
498
499     # find me all of the contents for a given suite
500     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
501                             s.section,
502                             b.package,
503                             b.architecture
504                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
505                    JOIN content_file_names n ON (c.filename=n.id)
506                    JOIN binaries b ON (b.id=c.binary_pkg)
507                    JOIN override o ON (o.package=b.package)
508                    JOIN section s ON (s.id=o.section)
509                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
510                    AND b.type=:overridetypename"""
511
512     vals = {'suiteid': suite.suite_id,
513             'overridetypeid': overridetype.overridetype_id,
514             'overridetypename': overridetype.overridetype}
515
516     if section is not None:
517         contents_q += " AND s.id = :sectionid"
518         vals['sectionid'] = section.section_id
519
520     contents_q += " ORDER BY fn"
521
522     ret = session.execute(contents_q, vals)
523
524     return ret
525
526 __all__.append('get_contents')
527
528 ################################################################################
529
530 class ContentFilepath(object):
531     def __init__(self, *args, **kwargs):
532         pass
533
534     def __repr__(self):
535         return '<ContentFilepath %s>' % self.filepath
536
537 __all__.append('ContentFilepath')
538
539 def get_or_set_contents_path_id(filepath, session=None):
540     """
541     Returns database id for given path.
542
543     If no matching file is found, a row is inserted.
544
545     @type filename: string
546     @param filename: The filepath
547     @type session: SQLAlchemy
548     @param session: Optional SQL session object (a temporary one will be
549     generated if not supplied).  If not passed, a commit will be performed at
550     the end of the function, otherwise the caller is responsible for commiting.
551
552     @rtype: int
553     @return: the database id for the given path
554     """
555     privatetrans = False
556     if session is None:
557         session = DBConn().session()
558         privatetrans = True
559
560     q = session.query(ContentFilepath).filter_by(filepath=filepath)
561     if q.count() < 1:
562         cf = ContentFilepath()
563         cf.filepath = filepath
564         session.add(cf)
565         if privatetrans:
566             session.commit()
567         else:
568             session.flush()
569         ret = cf.cafilepath_id
570     else:
571         ret = q.one().cafilepath_id
572
573     if privatetrans:
574         session.close()
575
576     return ret
577
578 __all__.append('get_or_set_contents_path_id')
579
580 ################################################################################
581
582 class ContentAssociation(object):
583     def __init__(self, *args, **kwargs):
584         pass
585
586     def __repr__(self):
587         return '<ContentAssociation %s>' % self.ca_id
588
589 __all__.append('ContentAssociation')
590
591 def insert_content_paths(binary_id, fullpaths, session=None):
592     """
593     Make sure given path is associated with given binary id
594
595     @type binary_id: int
596     @param binary_id: the id of the binary
597     @type fullpaths: list
598     @param fullpaths: the list of paths of the file being associated with the binary
599     @type session: SQLAlchemy session
600     @param session: Optional SQLAlchemy session.  If this is passed, the caller
601     is responsible for ensuring a transaction has begun and committing the
602     results or rolling back based on the result code.  If not passed, a commit
603     will be performed at the end of the function, otherwise the caller is
604     responsible for commiting.
605
606     @return: True upon success
607     """
608
609     privatetrans = False
610     if session is None:
611         session = DBConn().session()
612         privatetrans = True
613
614     try:
615         # Insert paths
616         pathcache = {}
617         for fullpath in fullpaths:
618             # Get the necessary IDs ...
619             (path, file) = os.path.split(fullpath)
620
621             filepath_id = get_or_set_contents_path_id(path, session)
622             filename_id = get_or_set_contents_file_id(file, session)
623
624             pathcache[fullpath] = (filepath_id, filename_id)
625
626         for fullpath, dat in pathcache.items():
627             ca = ContentAssociation()
628             ca.binary_id = binary_id
629             ca.filepath_id = dat[0]
630             ca.filename_id = dat[1]
631             session.add(ca)
632
633         # Only commit if we set up the session ourself
634         if privatetrans:
635             session.commit()
636             session.close()
637         else:
638             session.flush()
639
640         return True
641
642     except:
643         traceback.print_exc()
644
645         # Only rollback if we set up the session ourself
646         if privatetrans:
647             session.rollback()
648             session.close()
649
650         return False
651
652 __all__.append('insert_content_paths')
653
654 ################################################################################
655
656 class DSCFile(object):
657     def __init__(self, *args, **kwargs):
658         pass
659
660     def __repr__(self):
661         return '<DSCFile %s>' % self.dscfile_id
662
663 __all__.append('DSCFile')
664
665 @session_wrapper
666 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
667     """
668     Returns a list of DSCFiles which may be empty
669
670     @type dscfile_id: int (optional)
671     @param dscfile_id: the dscfile_id of the DSCFiles to find
672
673     @type source_id: int (optional)
674     @param source_id: the source id related to the DSCFiles to find
675
676     @type poolfile_id: int (optional)
677     @param poolfile_id: the poolfile id related to the DSCFiles to find
678
679     @rtype: list
680     @return: Possibly empty list of DSCFiles
681     """
682
683     q = session.query(DSCFile)
684
685     if dscfile_id is not None:
686         q = q.filter_by(dscfile_id=dscfile_id)
687
688     if source_id is not None:
689         q = q.filter_by(source_id=source_id)
690
691     if poolfile_id is not None:
692         q = q.filter_by(poolfile_id=poolfile_id)
693
694     ret = q.all()
695
696     return ret
697
698 __all__.append('get_dscfiles')
699
700 ################################################################################
701
702 class PoolFile(object):
703     def __init__(self, *args, **kwargs):
704         pass
705
706     def __repr__(self):
707         return '<PoolFile %s>' % self.filename
708
709 __all__.append('PoolFile')
710
711 @session_wrapper
712 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
713     """
714     Returns a tuple:
715      (ValidFileFound [boolean or None], PoolFile object or None)
716
717     @type filename: string
718     @param filename: the filename of the file to check against the DB
719
720     @type filesize: int
721     @param filesize: the size of the file to check against the DB
722
723     @type md5sum: string
724     @param md5sum: the md5sum of the file to check against the DB
725
726     @type location_id: int
727     @param location_id: the id of the location to look in
728
729     @rtype: tuple
730     @return: Tuple of length 2.
731              If more than one file found with that name:
732                     (None,  None)
733              If valid pool file found: (True, PoolFile object)
734              If valid pool file not found:
735                     (False, None) if no file found
736                     (False, PoolFile object) if file found with size/md5sum mismatch
737     """
738
739     q = session.query(PoolFile).filter_by(filename=filename)
740     q = q.join(Location).filter_by(location_id=location_id)
741
742     ret = None
743
744     if q.count() > 1:
745         ret = (None, None)
746     elif q.count() < 1:
747         ret = (False, None)
748     else:
749         obj = q.one()
750         if obj.md5sum != md5sum or obj.filesize != filesize:
751             ret = (False, obj)
752
753     if ret is None:
754         ret = (True, obj)
755
756     return ret
757
758 __all__.append('check_poolfile')
759
760 @session_wrapper
761 def get_poolfile_by_id(file_id, session=None):
762     """
763     Returns a PoolFile objects or None for the given id
764
765     @type file_id: int
766     @param file_id: the id of the file to look for
767
768     @rtype: PoolFile or None
769     @return: either the PoolFile object or None
770     """
771
772     q = session.query(PoolFile).filter_by(file_id=file_id)
773
774     if q.count() > 0:
775         ret = q.one()
776     else:
777         ret = None
778
779     return ret
780
781 __all__.append('get_poolfile_by_id')
782
783
784 @session_wrapper
785 def get_poolfile_by_name(filename, location_id=None, session=None):
786     """
787     Returns an array of PoolFile objects for the given filename and
788     (optionally) location_id
789
790     @type filename: string
791     @param filename: the filename of the file to check against the DB
792
793     @type location_id: int
794     @param location_id: the id of the location to look in (optional)
795
796     @rtype: array
797     @return: array of PoolFile objects
798     """
799
800     q = session.query(PoolFile).filter_by(filename=filename)
801
802     if location_id is not None:
803         q = q.join(Location).filter_by(location_id=location_id)
804
805     ret = q.all()
806
807     return ret
808
809 __all__.append('get_poolfile_by_name')
810
811 @session_wrapper
812 def get_poolfile_like_name(filename, session=None):
813     """
814     Returns an array of PoolFile objects which are like the given name
815
816     @type filename: string
817     @param filename: the filename of the file to check against the DB
818
819     @rtype: array
820     @return: array of PoolFile objects
821     """
822
823     # TODO: There must be a way of properly using bind parameters with %FOO%
824     q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
825
826     ret = q.all()
827
828     return ret
829
830 __all__.append('get_poolfile_like_name')
831
832 ################################################################################
833
834 class Fingerprint(object):
835     def __init__(self, *args, **kwargs):
836         pass
837
838     def __repr__(self):
839         return '<Fingerprint %s>' % self.fingerprint
840
841 __all__.append('Fingerprint')
842
843 def get_or_set_fingerprint(fpr, session=None):
844     """
845     Returns Fingerprint object for given fpr.
846
847     If no matching fpr is found, a row is inserted.
848
849     @type fpr: string
850     @param fpr: The fpr to find / add
851
852     @type session: SQLAlchemy
853     @param session: Optional SQL session object (a temporary one will be
854     generated if not supplied).  If not passed, a commit will be performed at
855     the end of the function, otherwise the caller is responsible for commiting.
856     A flush will be performed either way.
857
858     @rtype: Fingerprint
859     @return: the Fingerprint object for the given fpr
860     """
861     privatetrans = False
862     if session is None:
863         session = DBConn().session()
864         privatetrans = True
865
866     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
867     if q.count() < 1:
868         fingerprint = Fingerprint()
869         fingerprint.fingerprint = fpr
870         session.add(fingerprint)
871         if privatetrans:
872             session.commit()
873         else:
874             session.flush()
875         ret = fingerprint
876     else:
877         ret = q.one()
878
879     if privatetrans:
880         session.close()
881
882     return ret
883
884 __all__.append('get_or_set_fingerprint')
885
886 ################################################################################
887
888 class Keyring(object):
889     def __init__(self, *args, **kwargs):
890         pass
891
892     def __repr__(self):
893         return '<Keyring %s>' % self.keyring_name
894
895 __all__.append('Keyring')
896
897 def get_or_set_keyring(keyring, session=None):
898     """
899     If C{keyring} does not have an entry in the C{keyrings} table yet, create one
900     and return the new Keyring
901     If C{keyring} already has an entry, simply return the existing Keyring
902
903     @type keyring: string
904     @param keyring: the keyring name
905
906     @rtype: Keyring
907     @return: the Keyring object for this keyring
908
909     """
910     privatetrans = False
911     if session is None:
912         session = DBConn().session()
913         privatetrans = True
914
915     try:
916         obj = session.query(Keyring).filter_by(keyring_name=keyring).first()
917
918         if obj is None:
919             obj = Keyring(keyring_name=keyring)
920             session.add(obj)
921             if privatetrans:
922                 session.commit()
923             else:
924                 session.flush()
925
926         return obj
927     finally:
928         if privatetrans:
929             session.close()
930
931 __all__.append('get_or_set_keyring')
932
933 ################################################################################
934
935 class Location(object):
936     def __init__(self, *args, **kwargs):
937         pass
938
939     def __repr__(self):
940         return '<Location %s (%s)>' % (self.path, self.location_id)
941
942 __all__.append('Location')
943
944 @session_wrapper
945 def get_location(location, component=None, archive=None, session=None):
946     """
947     Returns Location object for the given combination of location, component
948     and archive
949
950     @type location: string
951     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
952
953     @type component: string
954     @param component: the component name (if None, no restriction applied)
955
956     @type archive: string
957     @param archive_id: the archive name (if None, no restriction applied)
958
959     @rtype: Location / None
960     @return: Either a Location object or None if one can't be found
961     """
962
963     q = session.query(Location).filter_by(path=location)
964
965     if archive is not None:
966         q = q.join(Archive).filter_by(archive_name=archive)
967
968     if component is not None:
969         q = q.join(Component).filter_by(component_name=component)
970
971     if q.count() < 1:
972         ret = None
973     else:
974         ret = q.one()
975
976     return ret
977
978 __all__.append('get_location')
979
980 ################################################################################
981
982 class Maintainer(object):
983     def __init__(self, *args, **kwargs):
984         pass
985
986     def __repr__(self):
987         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
988
989     def get_split_maintainer(self):
990         if not hasattr(self, 'name') or self.name is None:
991             return ('', '', '', '')
992
993         return fix_maintainer(self.name.strip())
994
995 __all__.append('Maintainer')
996
997 def get_or_set_maintainer(name, session=None):
998     """
999     Returns Maintainer object for given maintainer name.
1000
1001     If no matching maintainer name is found, a row is inserted.
1002
1003     @type name: string
1004     @param name: The maintainer name to add
1005
1006     @type session: SQLAlchemy
1007     @param session: Optional SQL session object (a temporary one will be
1008     generated if not supplied).  If not passed, a commit will be performed at
1009     the end of the function, otherwise the caller is responsible for commiting.
1010     A flush will be performed either way.
1011
1012     @rtype: Maintainer
1013     @return: the Maintainer object for the given maintainer
1014     """
1015     privatetrans = False
1016     if session is None:
1017         session = DBConn().session()
1018         privatetrans = True
1019
1020     q = session.query(Maintainer).filter_by(name=name)
1021     if q.count() < 1:
1022         maintainer = Maintainer()
1023         maintainer.name = name
1024         session.add(maintainer)
1025         if privatetrans:
1026             session.commit()
1027         else:
1028             session.flush()
1029         ret = maintainer
1030     else:
1031         ret = q.one()
1032
1033     if privatetrans:
1034         session.close()
1035
1036     return ret
1037
1038 __all__.append('get_or_set_maintainer')
1039
1040 def get_maintainer(maintainer_id, session=None):
1041     """
1042     Return the name of the maintainer behind C{maintainer_id} or None if that
1043     maintainer_id is invalid.
1044
1045     @type maintainer_id: int
1046     @param maintainer_id: the id of the maintainer
1047
1048     @rtype: Maintainer
1049     @return: the Maintainer with this C{maintainer_id}
1050     """
1051
1052     privatetrans = False
1053     if session is None:
1054         session = DBConn().session()
1055         privatetrans = True
1056
1057     try:
1058         return session.query(Maintainer).get(maintainer_id)
1059     finally:
1060         if privatetrans:
1061             session.close()
1062
1063 __all__.append('get_maintainer')
1064
1065 ################################################################################
1066
1067 class NewComment(object):
1068     def __init__(self, *args, **kwargs):
1069         pass
1070
1071     def __repr__(self):
1072         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1073
1074 __all__.append('NewComment')
1075
1076 @session_wrapper
1077 def has_new_comment(package, version, session=None):
1078     """
1079     Returns true if the given combination of C{package}, C{version} has a comment.
1080
1081     @type package: string
1082     @param package: name of the package
1083
1084     @type version: string
1085     @param version: package version
1086
1087     @type session: Session
1088     @param session: Optional SQLA session object (a temporary one will be
1089     generated if not supplied)
1090
1091     @rtype: boolean
1092     @return: true/false
1093     """
1094
1095     q = session.query(NewComment)
1096     q = q.filter_by(package=package)
1097     q = q.filter_by(version=version)
1098
1099     ret = q.count() > 0
1100
1101     return ret
1102
1103 __all__.append('has_new_comment')
1104
1105 @session_wrapper
1106 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1107     """
1108     Returns (possibly empty) list of NewComment objects for the given
1109     parameters
1110
1111     @type package: string (optional)
1112     @param package: name of the package
1113
1114     @type version: string (optional)
1115     @param version: package version
1116
1117     @type comment_id: int (optional)
1118     @param comment_id: An id of a comment
1119
1120     @type session: Session
1121     @param session: Optional SQLA session object (a temporary one will be
1122     generated if not supplied)
1123
1124     @rtype: list
1125     @return: A (possibly empty) list of NewComment objects will be returned
1126     """
1127
1128     q = session.query(NewComment)
1129     if package is not None: q = q.filter_by(package=package)
1130     if version is not None: q = q.filter_by(version=version)
1131     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1132
1133     ret = q.all()
1134
1135     return ret
1136
1137 __all__.append('get_new_comments')
1138
1139 ################################################################################
1140
1141 class Override(object):
1142     def __init__(self, *args, **kwargs):
1143         pass
1144
1145     def __repr__(self):
1146         return '<Override %s (%s)>' % (self.package, self.suite_id)
1147
1148 __all__.append('Override')
1149
1150 @session_wrapper
1151 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1152     """
1153     Returns Override object for the given parameters
1154
1155     @type package: string
1156     @param package: The name of the package
1157
1158     @type suite: string, list or None
1159     @param suite: The name of the suite (or suites if a list) to limit to.  If
1160                   None, don't limit.  Defaults to None.
1161
1162     @type component: string, list or None
1163     @param component: The name of the component (or components if a list) to
1164                       limit to.  If None, don't limit.  Defaults to None.
1165
1166     @type overridetype: string, list or None
1167     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1168                          limit to.  If None, don't limit.  Defaults to None.
1169
1170     @type session: Session
1171     @param session: Optional SQLA session object (a temporary one will be
1172     generated if not supplied)
1173
1174     @rtype: list
1175     @return: A (possibly empty) list of Override objects will be returned
1176     """
1177
1178     q = session.query(Override)
1179     q = q.filter_by(package=package)
1180
1181     if suite is not None:
1182         if not isinstance(suite, list): suite = [suite]
1183         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1184
1185     if component is not None:
1186         if not isinstance(component, list): component = [component]
1187         q = q.join(Component).filter(Component.component_name.in_(component))
1188
1189     if overridetype is not None:
1190         if not isinstance(overridetype, list): overridetype = [overridetype]
1191         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1192
1193     ret = q.all()
1194
1195     return ret
1196
1197 __all__.append('get_override')
1198
1199
1200 ################################################################################
1201
1202 class OverrideType(object):
1203     def __init__(self, *args, **kwargs):
1204         pass
1205
1206     def __repr__(self):
1207         return '<OverrideType %s>' % self.overridetype
1208
1209 __all__.append('OverrideType')
1210
1211 @session_wrapper
1212 def get_override_type(override_type, session=None):
1213     """
1214     Returns OverrideType object for given C{override type}.
1215
1216     @type override_type: string
1217     @param override_type: The name of the override type
1218
1219     @type session: Session
1220     @param session: Optional SQLA session object (a temporary one will be
1221     generated if not supplied)
1222
1223     @rtype: int
1224     @return: the database id for the given override type
1225     """
1226
1227     q = session.query(OverrideType).filter_by(overridetype=override_type)
1228
1229     if q.count() == 0:
1230         ret = None
1231     else:
1232         ret = q.one()
1233
1234     return ret
1235
1236 __all__.append('get_override_type')
1237
1238 ################################################################################
1239
1240 class PendingContentAssociation(object):
1241     def __init__(self, *args, **kwargs):
1242         pass
1243
1244     def __repr__(self):
1245         return '<PendingContentAssociation %s>' % self.pca_id
1246
1247 __all__.append('PendingContentAssociation')
1248
1249 def insert_pending_content_paths(package, fullpaths, session=None):
1250     """
1251     Make sure given paths are temporarily associated with given
1252     package
1253
1254     @type package: dict
1255     @param package: the package to associate with should have been read in from the binary control file
1256     @type fullpaths: list
1257     @param fullpaths: the list of paths of the file being associated with the binary
1258     @type session: SQLAlchemy session
1259     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1260     is responsible for ensuring a transaction has begun and committing the
1261     results or rolling back based on the result code.  If not passed, a commit
1262     will be performed at the end of the function
1263
1264     @return: True upon success, False if there is a problem
1265     """
1266
1267     privatetrans = False
1268
1269     if session is None:
1270         session = DBConn().session()
1271         privatetrans = True
1272
1273     try:
1274         arch = get_architecture(package['Architecture'], session)
1275         arch_id = arch.arch_id
1276
1277         # Remove any already existing recorded files for this package
1278         q = session.query(PendingContentAssociation)
1279         q = q.filter_by(package=package['Package'])
1280         q = q.filter_by(version=package['Version'])
1281         q = q.filter_by(architecture=arch_id)
1282         q.delete()
1283
1284         # Insert paths
1285         pathcache = {}
1286         for fullpath in fullpaths:
1287             (path, file) = os.path.split(fullpath)
1288
1289             if path.startswith( "./" ):
1290                 path = path[2:]
1291
1292             filepath_id = get_or_set_contents_path_id(path, session)
1293             filename_id = get_or_set_contents_file_id(file, session)
1294
1295             pathcache[fullpath] = (filepath_id, filename_id)
1296
1297         for fullpath, dat in pathcache.items():
1298             pca = PendingContentAssociation()
1299             pca.package = package['Package']
1300             pca.version = package['Version']
1301             pca.filepath_id = dat[0]
1302             pca.filename_id = dat[1]
1303             pca.architecture = arch_id
1304             session.add(pca)
1305
1306         # Only commit if we set up the session ourself
1307         if privatetrans:
1308             session.commit()
1309             session.close()
1310         else:
1311             session.flush()
1312
1313         return True
1314     except Exception, e:
1315         traceback.print_exc()
1316
1317         # Only rollback if we set up the session ourself
1318         if privatetrans:
1319             session.rollback()
1320             session.close()
1321
1322         return False
1323
1324 __all__.append('insert_pending_content_paths')
1325
1326 ################################################################################
1327
1328 class Priority(object):
1329     def __init__(self, *args, **kwargs):
1330         pass
1331
1332     def __eq__(self, val):
1333         if isinstance(val, str):
1334             return (self.priority == val)
1335         # This signals to use the normal comparison operator
1336         return NotImplemented
1337
1338     def __ne__(self, val):
1339         if isinstance(val, str):
1340             return (self.priority != val)
1341         # This signals to use the normal comparison operator
1342         return NotImplemented
1343
1344     def __repr__(self):
1345         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1346
1347 __all__.append('Priority')
1348
1349 @session_wrapper
1350 def get_priority(priority, session=None):
1351     """
1352     Returns Priority object for given C{priority name}.
1353
1354     @type priority: string
1355     @param priority: The name of the priority
1356
1357     @type session: Session
1358     @param session: Optional SQLA session object (a temporary one will be
1359     generated if not supplied)
1360
1361     @rtype: Priority
1362     @return: Priority object for the given priority
1363     """
1364
1365     q = session.query(Priority).filter_by(priority=priority)
1366
1367     if q.count() == 0:
1368         ret = None
1369     else:
1370         ret = q.one()
1371
1372     return ret
1373
1374 __all__.append('get_priority')
1375
1376 @session_wrapper
1377 def get_priorities(session=None):
1378     """
1379     Returns dictionary of priority names -> id mappings
1380
1381     @type session: Session
1382     @param session: Optional SQL session object (a temporary one will be
1383     generated if not supplied)
1384
1385     @rtype: dictionary
1386     @return: dictionary of priority names -> id mappings
1387     """
1388
1389     ret = {}
1390     q = session.query(Priority)
1391     for x in q.all():
1392         ret[x.priority] = x.priority_id
1393
1394     return ret
1395
1396 __all__.append('get_priorities')
1397
1398 ################################################################################
1399
1400 class Queue(object):
1401     def __init__(self, *args, **kwargs):
1402         pass
1403
1404     def __repr__(self):
1405         return '<Queue %s>' % self.queue_name
1406
1407     def autobuild_upload(self, changes, srcpath, session=None):
1408         """
1409         Update queue_build database table used for incoming autobuild support.
1410
1411         @type changes: Changes
1412         @param changes: changes object for the upload to process
1413
1414         @type srcpath: string
1415         @param srcpath: path for the queue file entries/link destinations
1416
1417         @type session: SQLAlchemy session
1418         @param session: Optional SQLAlchemy session.  If this is passed, the
1419         caller is responsible for ensuring a transaction has begun and
1420         committing the results or rolling back based on the result code.  If
1421         not passed, a commit will be performed at the end of the function,
1422         otherwise the caller is responsible for commiting.
1423
1424         @rtype: NoneType or string
1425         @return: None if the operation failed, a string describing the error if not
1426         """
1427
1428         privatetrans = False
1429         if session is None:
1430             session = DBConn().session()
1431             privatetrans = True
1432
1433         # TODO: Remove by moving queue config into the database
1434         conf = Config()
1435
1436         for suitename in changes.changes["distribution"].keys():
1437             # TODO: Move into database as:
1438             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1439             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1440             #       This also gets rid of the SecurityQueueBuild hack below
1441             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1442                 continue
1443
1444             # Find suite object
1445             s = get_suite(suitename, session)
1446             if s is None:
1447                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1448
1449             # TODO: Get from database as above
1450             dest_dir = conf["Dir::QueueBuild"]
1451
1452             # TODO: Move into database as above
1453             if conf.FindB("Dinstall::SecurityQueueBuild"):
1454                 dest_dir = os.path.join(dest_dir, suitename)
1455
1456             for file_entry in changes.files.keys():
1457                 src = os.path.join(srcpath, file_entry)
1458                 dest = os.path.join(dest_dir, file_entry)
1459
1460                 # TODO: Move into database as above
1461                 if conf.FindB("Dinstall::SecurityQueueBuild"):
1462                     # Copy it since the original won't be readable by www-data
1463                     utils.copy(src, dest)
1464                 else:
1465                     # Create a symlink to it
1466                     os.symlink(src, dest)
1467
1468                 qb = QueueBuild()
1469                 qb.suite_id = s.suite_id
1470                 qb.queue_id = self.queue_id
1471                 qb.filename = dest
1472                 qb.in_queue = True
1473
1474                 session.add(qb)
1475
1476             # If the .orig.tar.gz is in the pool, create a symlink to
1477             # it (if one doesn't already exist)
1478             if changes.orig_tar_id:
1479                 # Determine the .orig.tar.gz file name
1480                 for dsc_file in changes.dsc_files.keys():
1481                     if dsc_file.endswith(".orig.tar.gz"):
1482                         filename = dsc_file
1483
1484                 dest = os.path.join(dest_dir, filename)
1485
1486                 # If it doesn't exist, create a symlink
1487                 if not os.path.exists(dest):
1488                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1489                                         {'id': changes.orig_tar_id})
1490                     res = q.fetchone()
1491                     if not res:
1492                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1493
1494                     src = os.path.join(res[0], res[1])
1495                     os.symlink(src, dest)
1496
1497                     # Add it to the list of packages for later processing by apt-ftparchive
1498                     qb = QueueBuild()
1499                     qb.suite_id = s.suite_id
1500                     qb.queue_id = self.queue_id
1501                     qb.filename = dest
1502                     qb.in_queue = True
1503                     session.add(qb)
1504
1505                 # If it does, update things to ensure it's not removed prematurely
1506                 else:
1507                     qb = get_queue_build(dest, s.suite_id, session)
1508                     if qb is None:
1509                         qb.in_queue = True
1510                         qb.last_used = None
1511                         session.add(qb)
1512
1513         if privatetrans:
1514             session.commit()
1515             session.close()
1516
1517         return None
1518
1519 __all__.append('Queue')
1520
1521 @session_wrapper
1522 def get_queue(queuename, session=None):
1523     """
1524     Returns Queue object for given C{queue name}.
1525
1526     @type queuename: string
1527     @param queuename: The name of the queue
1528
1529     @type session: Session
1530     @param session: Optional SQLA session object (a temporary one will be
1531     generated if not supplied)
1532
1533     @rtype: Queue
1534     @return: Queue object for the given queue
1535     """
1536
1537     q = session.query(Queue).filter_by(queue_name=queuename)
1538     if q.count() == 0:
1539         ret = None
1540     else:
1541         ret = q.one()
1542
1543     return ret
1544
1545 __all__.append('get_queue')
1546
1547 ################################################################################
1548
1549 class QueueBuild(object):
1550     def __init__(self, *args, **kwargs):
1551         pass
1552
1553     def __repr__(self):
1554         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1555
1556 __all__.append('QueueBuild')
1557
1558 @session_wrapper
1559 def get_queue_build(filename, suite, session=None):
1560     """
1561     Returns QueueBuild object for given C{filename} and C{suite}.
1562
1563     @type filename: string
1564     @param filename: The name of the file
1565
1566     @type suiteid: int or str
1567     @param suiteid: Suite name or ID
1568
1569     @type session: Session
1570     @param session: Optional SQLA session object (a temporary one will be
1571     generated if not supplied)
1572
1573     @rtype: Queue
1574     @return: Queue object for the given queue
1575     """
1576
1577     if isinstance(suite, int):
1578         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1579     else:
1580         q = session.query(QueueBuild).filter_by(filename=filename)
1581         q = q.join(Suite).filter_by(suite_name=suite)
1582
1583     if q.count() == 0:
1584         ret = None
1585     else:
1586         ret = q.one()
1587
1588     return ret
1589
1590 __all__.append('get_queue_build')
1591
1592 ################################################################################
1593
1594 class Section(object):
1595     def __init__(self, *args, **kwargs):
1596         pass
1597
1598     def __eq__(self, val):
1599         if isinstance(val, str):
1600             return (self.section == val)
1601         # This signals to use the normal comparison operator
1602         return NotImplemented
1603
1604     def __ne__(self, val):
1605         if isinstance(val, str):
1606             return (self.section != val)
1607         # This signals to use the normal comparison operator
1608         return NotImplemented
1609
1610     def __repr__(self):
1611         return '<Section %s>' % self.section
1612
1613 __all__.append('Section')
1614
1615 @session_wrapper
1616 def get_section(section, session=None):
1617     """
1618     Returns Section object for given C{section name}.
1619
1620     @type section: string
1621     @param section: The name of the section
1622
1623     @type session: Session
1624     @param session: Optional SQLA session object (a temporary one will be
1625     generated if not supplied)
1626
1627     @rtype: Section
1628     @return: Section object for the given section name
1629     """
1630
1631     q = session.query(Section).filter_by(section=section)
1632     if q.count() == 0:
1633         ret = None
1634     else:
1635         ret = q.one()
1636
1637     return ret
1638
1639 __all__.append('get_section')
1640
1641 @session_wrapper
1642 def get_sections(session=None):
1643     """
1644     Returns dictionary of section names -> id mappings
1645
1646     @type session: Session
1647     @param session: Optional SQL session object (a temporary one will be
1648     generated if not supplied)
1649
1650     @rtype: dictionary
1651     @return: dictionary of section names -> id mappings
1652     """
1653
1654     ret = {}
1655     q = session.query(Section)
1656     for x in q.all():
1657         ret[x.section] = x.section_id
1658
1659     return ret
1660
1661 __all__.append('get_sections')
1662
1663 ################################################################################
1664
1665 class DBSource(object):
1666     def __init__(self, *args, **kwargs):
1667         pass
1668
1669     def __repr__(self):
1670         return '<DBSource %s (%s)>' % (self.source, self.version)
1671
1672 __all__.append('DBSource')
1673
1674 @session_wrapper
1675 def source_exists(source, source_version, suites = ["any"], session=None):
1676     """
1677     Ensure that source exists somewhere in the archive for the binary
1678     upload being processed.
1679       1. exact match     => 1.0-3
1680       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1681
1682     @type package: string
1683     @param package: package source name
1684
1685     @type source_version: string
1686     @param source_version: expected source version
1687
1688     @type suites: list
1689     @param suites: list of suites to check in, default I{any}
1690
1691     @type session: Session
1692     @param session: Optional SQLA session object (a temporary one will be
1693     generated if not supplied)
1694
1695     @rtype: int
1696     @return: returns 1 if a source with expected version is found, otherwise 0
1697
1698     """
1699
1700     cnf = Config()
1701     ret = 1
1702
1703     for suite in suites:
1704         q = session.query(DBSource).filter_by(source=source)
1705         if suite != "any":
1706             # source must exist in suite X, or in some other suite that's
1707             # mapped to X, recursively... silent-maps are counted too,
1708             # unreleased-maps aren't.
1709             maps = cnf.ValueList("SuiteMappings")[:]
1710             maps.reverse()
1711             maps = [ m.split() for m in maps ]
1712             maps = [ (x[1], x[2]) for x in maps
1713                             if x[0] == "map" or x[0] == "silent-map" ]
1714             s = [suite]
1715             for x in maps:
1716                 if x[1] in s and x[0] not in s:
1717                     s.append(x[0])
1718
1719             q = q.join(SrcAssociation).join(Suite)
1720             q = q.filter(Suite.suite_name.in_(s))
1721
1722         # Reduce the query results to a list of version numbers
1723         ql = [ j.version for j in q.all() ]
1724
1725         # Try (1)
1726         if source_version in ql:
1727             continue
1728
1729         # Try (2)
1730         from daklib.regexes import re_bin_only_nmu
1731         orig_source_version = re_bin_only_nmu.sub('', source_version)
1732         if orig_source_version in ql:
1733             continue
1734
1735         # No source found so return not ok
1736         ret = 0
1737
1738     return ret
1739
1740 __all__.append('source_exists')
1741
1742 @session_wrapper
1743 def get_suites_source_in(source, session=None):
1744     """
1745     Returns list of Suite objects which given C{source} name is in
1746
1747     @type source: str
1748     @param source: DBSource package name to search for
1749
1750     @rtype: list
1751     @return: list of Suite objects for the given source
1752     """
1753
1754     ret = session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1755
1756     return ret
1757
1758 __all__.append('get_suites_source_in')
1759
1760 @session_wrapper
1761 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1762     """
1763     Returns list of DBSource objects for given C{source} name and other parameters
1764
1765     @type source: str
1766     @param source: DBSource package name to search for
1767
1768     @type source: str or None
1769     @param source: DBSource version name to search for or None if not applicable
1770
1771     @type dm_upload_allowed: bool
1772     @param dm_upload_allowed: If None, no effect.  If True or False, only
1773     return packages with that dm_upload_allowed setting
1774
1775     @type session: Session
1776     @param session: Optional SQL session object (a temporary one will be
1777     generated if not supplied)
1778
1779     @rtype: list
1780     @return: list of DBSource objects for the given name (may be empty)
1781     """
1782
1783     q = session.query(DBSource).filter_by(source=source)
1784
1785     if version is not None:
1786         q = q.filter_by(version=version)
1787
1788     if dm_upload_allowed is not None:
1789         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1790
1791     ret = q.all()
1792
1793     return ret
1794
1795 __all__.append('get_sources_from_name')
1796
1797 @session_wrapper
1798 def get_source_in_suite(source, suite, session=None):
1799     """
1800     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1801
1802       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1803       - B{suite} - a suite name, eg. I{unstable}
1804
1805     @type source: string
1806     @param source: source package name
1807
1808     @type suite: string
1809     @param suite: the suite name
1810
1811     @rtype: string
1812     @return: the version for I{source} in I{suite}
1813
1814     """
1815
1816     q = session.query(SrcAssociation)
1817     q = q.join('source').filter_by(source=source)
1818     q = q.join('suite').filter_by(suite_name=suite)
1819
1820     if q.count() == 0:
1821         ret =  None
1822     else:
1823         # ???: Maybe we should just return the SrcAssociation object instead
1824         ret = q.one().source
1825
1826     return ret
1827
1828 __all__.append('get_source_in_suite')
1829
1830 ################################################################################
1831
1832 class SrcAssociation(object):
1833     def __init__(self, *args, **kwargs):
1834         pass
1835
1836     def __repr__(self):
1837         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1838
1839 __all__.append('SrcAssociation')
1840
1841 ################################################################################
1842
1843 class SrcUploader(object):
1844     def __init__(self, *args, **kwargs):
1845         pass
1846
1847     def __repr__(self):
1848         return '<SrcUploader %s>' % self.uploader_id
1849
1850 __all__.append('SrcUploader')
1851
1852 ################################################################################
1853
1854 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1855                  ('SuiteID', 'suite_id'),
1856                  ('Version', 'version'),
1857                  ('Origin', 'origin'),
1858                  ('Label', 'label'),
1859                  ('Description', 'description'),
1860                  ('Untouchable', 'untouchable'),
1861                  ('Announce', 'announce'),
1862                  ('Codename', 'codename'),
1863                  ('OverrideCodename', 'overridecodename'),
1864                  ('ValidTime', 'validtime'),
1865                  ('Priority', 'priority'),
1866                  ('NotAutomatic', 'notautomatic'),
1867                  ('CopyChanges', 'copychanges'),
1868                  ('CopyDotDak', 'copydotdak'),
1869                  ('CommentsDir', 'commentsdir'),
1870                  ('OverrideSuite', 'overridesuite'),
1871                  ('ChangelogBase', 'changelogbase')]
1872
1873
1874 class Suite(object):
1875     def __init__(self, *args, **kwargs):
1876         pass
1877
1878     def __repr__(self):
1879         return '<Suite %s>' % self.suite_name
1880
1881     def __eq__(self, val):
1882         if isinstance(val, str):
1883             return (self.suite_name == val)
1884         # This signals to use the normal comparison operator
1885         return NotImplemented
1886
1887     def __ne__(self, val):
1888         if isinstance(val, str):
1889             return (self.suite_name != val)
1890         # This signals to use the normal comparison operator
1891         return NotImplemented
1892
1893     def details(self):
1894         ret = []
1895         for disp, field in SUITE_FIELDS:
1896             val = getattr(self, field, None)
1897             if val is not None:
1898                 ret.append("%s: %s" % (disp, val))
1899
1900         return "\n".join(ret)
1901
1902 __all__.append('Suite')
1903
1904 @session_wrapper
1905 def get_suite_architecture(suite, architecture, session=None):
1906     """
1907     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1908     doesn't exist
1909
1910     @type suite: str
1911     @param suite: Suite name to search for
1912
1913     @type architecture: str
1914     @param architecture: Architecture name to search for
1915
1916     @type session: Session
1917     @param session: Optional SQL session object (a temporary one will be
1918     generated if not supplied)
1919
1920     @rtype: SuiteArchitecture
1921     @return: the SuiteArchitecture object or None
1922     """
1923
1924     q = session.query(SuiteArchitecture)
1925     q = q.join(Architecture).filter_by(arch_string=architecture)
1926     q = q.join(Suite).filter_by(suite_name=suite)
1927
1928     if q.count() == 0:
1929         ret = None
1930     else:
1931         ret = q.one()
1932
1933     return ret
1934
1935 __all__.append('get_suite_architecture')
1936
1937 @session_wrapper
1938 def get_suite(suite, session=None):
1939     """
1940     Returns Suite object for given C{suite name}.
1941
1942     @type suite: string
1943     @param suite: The name of the suite
1944
1945     @type session: Session
1946     @param session: Optional SQLA session object (a temporary one will be
1947     generated if not supplied)
1948
1949     @rtype: Suite
1950     @return: Suite object for the requested suite name (None if not presenT)
1951     """
1952
1953     q = session.query(Suite).filter_by(suite_name=suite)
1954
1955     if q.count() == 0:
1956         ret = None
1957     else:
1958         ret = q.one()
1959
1960     return ret
1961
1962 __all__.append('get_suite')
1963
1964 ################################################################################
1965
1966 class SuiteArchitecture(object):
1967     def __init__(self, *args, **kwargs):
1968         pass
1969
1970     def __repr__(self):
1971         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1972
1973 __all__.append('SuiteArchitecture')
1974
1975 @session_wrapper
1976 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1977     """
1978     Returns list of Architecture objects for given C{suite} name
1979
1980     @type source: str
1981     @param source: Suite name to search for
1982
1983     @type skipsrc: boolean
1984     @param skipsrc: Whether to skip returning the 'source' architecture entry
1985     (Default False)
1986
1987     @type skipall: boolean
1988     @param skipall: Whether to skip returning the 'all' architecture entry
1989     (Default False)
1990
1991     @type session: Session
1992     @param session: Optional SQL session object (a temporary one will be
1993     generated if not supplied)
1994
1995     @rtype: list
1996     @return: list of Architecture objects for the given name (may be empty)
1997     """
1998
1999     q = session.query(Architecture)
2000     q = q.join(SuiteArchitecture)
2001     q = q.join(Suite).filter_by(suite_name=suite)
2002
2003     if skipsrc:
2004         q = q.filter(Architecture.arch_string != 'source')
2005
2006     if skipall:
2007         q = q.filter(Architecture.arch_string != 'all')
2008
2009     q = q.order_by('arch_string')
2010
2011     ret = q.all()
2012
2013     return ret
2014
2015 __all__.append('get_suite_architectures')
2016
2017 ################################################################################
2018
2019 class Uid(object):
2020     def __init__(self, *args, **kwargs):
2021         pass
2022
2023     def __eq__(self, val):
2024         if isinstance(val, str):
2025             return (self.uid == val)
2026         # This signals to use the normal comparison operator
2027         return NotImplemented
2028
2029     def __ne__(self, val):
2030         if isinstance(val, str):
2031             return (self.uid != val)
2032         # This signals to use the normal comparison operator
2033         return NotImplemented
2034
2035     def __repr__(self):
2036         return '<Uid %s (%s)>' % (self.uid, self.name)
2037
2038 __all__.append('Uid')
2039
2040 def add_database_user(uidname, session=None):
2041     """
2042     Adds a database user
2043
2044     @type uidname: string
2045     @param uidname: The uid of the user to add
2046
2047     @type session: SQLAlchemy
2048     @param session: Optional SQL session object (a temporary one will be
2049     generated if not supplied).  If not passed, a commit will be performed at
2050     the end of the function, otherwise the caller is responsible for commiting.
2051
2052     @rtype: Uid
2053     @return: the uid object for the given uidname
2054     """
2055
2056     privatetrans = False
2057     if session is None:
2058         session = DBConn().session()
2059         privatetrans = True
2060
2061     session.execute("CREATE USER :uid", {'uid': uidname})
2062
2063     if privatetrans:
2064         session.commit()
2065         session.close()
2066
2067 __all__.append('add_database_user')
2068
2069 def get_or_set_uid(uidname, session=None):
2070     """
2071     Returns uid object for given uidname.
2072
2073     If no matching uidname is found, a row is inserted.
2074
2075     @type uidname: string
2076     @param uidname: The uid to add
2077
2078     @type session: SQLAlchemy
2079     @param session: Optional SQL session object (a temporary one will be
2080     generated if not supplied).  If not passed, a commit will be performed at
2081     the end of the function, otherwise the caller is responsible for commiting.
2082
2083     @rtype: Uid
2084     @return: the uid object for the given uidname
2085     """
2086
2087     privatetrans = False
2088     if session is None:
2089         session = DBConn().session()
2090         privatetrans = True
2091
2092     q = session.query(Uid).filter_by(uid=uidname)
2093
2094     if q.count() < 1:
2095         uid = Uid()
2096         uid.uid = uidname
2097         session.add(uid)
2098         if privatetrans:
2099             session.commit()
2100         else:
2101             session.flush()
2102         ret = uid
2103     else:
2104         ret = q.one()
2105
2106     if privatetrans:
2107         session.close()
2108
2109     return ret
2110
2111 __all__.append('get_or_set_uid')
2112
2113 @session_wrapper
2114 def get_uid_from_fingerprint(fpr, session=None):
2115     q = session.query(Uid)
2116     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2117
2118     if q.count() != 1:
2119         ret = None
2120     else:
2121         ret = q.one()
2122
2123     return ret
2124
2125 __all__.append('get_uid_from_fingerprint')
2126
2127 ################################################################################
2128
2129 class DBConn(Singleton):
2130     """
2131     database module init.
2132     """
2133     def __init__(self, *args, **kwargs):
2134         super(DBConn, self).__init__(*args, **kwargs)
2135
2136     def _startup(self, *args, **kwargs):
2137         self.debug = False
2138         if kwargs.has_key('debug'):
2139             self.debug = True
2140         self.__createconn()
2141
2142     def __setuptables(self):
2143         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2144         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2145         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2146         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2147         self.tbl_component = Table('component', self.db_meta, autoload=True)
2148         self.tbl_config = Table('config', self.db_meta, autoload=True)
2149         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2150         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2151         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2152         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2153         self.tbl_files = Table('files', self.db_meta, autoload=True)
2154         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2155         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2156         self.tbl_location = Table('location', self.db_meta, autoload=True)
2157         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2158         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2159         self.tbl_override = Table('override', self.db_meta, autoload=True)
2160         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2161         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2162         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2163         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2164         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2165         self.tbl_section = Table('section', self.db_meta, autoload=True)
2166         self.tbl_source = Table('source', self.db_meta, autoload=True)
2167         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2168         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2169         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2170         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2171         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2172
2173     def __setupmappers(self):
2174         mapper(Architecture, self.tbl_architecture,
2175                properties = dict(arch_id = self.tbl_architecture.c.id))
2176
2177         mapper(Archive, self.tbl_archive,
2178                properties = dict(archive_id = self.tbl_archive.c.id,
2179                                  archive_name = self.tbl_archive.c.name))
2180
2181         mapper(BinAssociation, self.tbl_bin_associations,
2182                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2183                                  suite_id = self.tbl_bin_associations.c.suite,
2184                                  suite = relation(Suite),
2185                                  binary_id = self.tbl_bin_associations.c.bin,
2186                                  binary = relation(DBBinary)))
2187
2188         mapper(DBBinary, self.tbl_binaries,
2189                properties = dict(binary_id = self.tbl_binaries.c.id,
2190                                  package = self.tbl_binaries.c.package,
2191                                  version = self.tbl_binaries.c.version,
2192                                  maintainer_id = self.tbl_binaries.c.maintainer,
2193                                  maintainer = relation(Maintainer),
2194                                  source_id = self.tbl_binaries.c.source,
2195                                  source = relation(DBSource),
2196                                  arch_id = self.tbl_binaries.c.architecture,
2197                                  architecture = relation(Architecture),
2198                                  poolfile_id = self.tbl_binaries.c.file,
2199                                  poolfile = relation(PoolFile),
2200                                  binarytype = self.tbl_binaries.c.type,
2201                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2202                                  fingerprint = relation(Fingerprint),
2203                                  install_date = self.tbl_binaries.c.install_date,
2204                                  binassociations = relation(BinAssociation,
2205                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2206
2207         mapper(Component, self.tbl_component,
2208                properties = dict(component_id = self.tbl_component.c.id,
2209                                  component_name = self.tbl_component.c.name))
2210
2211         mapper(DBConfig, self.tbl_config,
2212                properties = dict(config_id = self.tbl_config.c.id))
2213
2214         mapper(ContentAssociation, self.tbl_content_associations,
2215                properties = dict(ca_id = self.tbl_content_associations.c.id,
2216                                  filename_id = self.tbl_content_associations.c.filename,
2217                                  filename    = relation(ContentFilename),
2218                                  filepath_id = self.tbl_content_associations.c.filepath,
2219                                  filepath    = relation(ContentFilepath),
2220                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
2221                                  binary      = relation(DBBinary)))
2222
2223
2224         mapper(ContentFilename, self.tbl_content_file_names,
2225                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2226                                  filename = self.tbl_content_file_names.c.file))
2227
2228         mapper(ContentFilepath, self.tbl_content_file_paths,
2229                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2230                                  filepath = self.tbl_content_file_paths.c.path))
2231
2232         mapper(DSCFile, self.tbl_dsc_files,
2233                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2234                                  source_id = self.tbl_dsc_files.c.source,
2235                                  source = relation(DBSource),
2236                                  poolfile_id = self.tbl_dsc_files.c.file,
2237                                  poolfile = relation(PoolFile)))
2238
2239         mapper(PoolFile, self.tbl_files,
2240                properties = dict(file_id = self.tbl_files.c.id,
2241                                  filesize = self.tbl_files.c.size,
2242                                  location_id = self.tbl_files.c.location,
2243                                  location = relation(Location)))
2244
2245         mapper(Fingerprint, self.tbl_fingerprint,
2246                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2247                                  uid_id = self.tbl_fingerprint.c.uid,
2248                                  uid = relation(Uid),
2249                                  keyring_id = self.tbl_fingerprint.c.keyring,
2250                                  keyring = relation(Keyring)))
2251
2252         mapper(Keyring, self.tbl_keyrings,
2253                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2254                                  keyring_id = self.tbl_keyrings.c.id))
2255
2256         mapper(Location, self.tbl_location,
2257                properties = dict(location_id = self.tbl_location.c.id,
2258                                  component_id = self.tbl_location.c.component,
2259                                  component = relation(Component),
2260                                  archive_id = self.tbl_location.c.archive,
2261                                  archive = relation(Archive),
2262                                  archive_type = self.tbl_location.c.type))
2263
2264         mapper(Maintainer, self.tbl_maintainer,
2265                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2266
2267         mapper(NewComment, self.tbl_new_comments,
2268                properties = dict(comment_id = self.tbl_new_comments.c.id))
2269
2270         mapper(Override, self.tbl_override,
2271                properties = dict(suite_id = self.tbl_override.c.suite,
2272                                  suite = relation(Suite),
2273                                  component_id = self.tbl_override.c.component,
2274                                  component = relation(Component),
2275                                  priority_id = self.tbl_override.c.priority,
2276                                  priority = relation(Priority),
2277                                  section_id = self.tbl_override.c.section,
2278                                  section = relation(Section),
2279                                  overridetype_id = self.tbl_override.c.type,
2280                                  overridetype = relation(OverrideType)))
2281
2282         mapper(OverrideType, self.tbl_override_type,
2283                properties = dict(overridetype = self.tbl_override_type.c.type,
2284                                  overridetype_id = self.tbl_override_type.c.id))
2285
2286         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2287                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2288                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
2289                                  filepath = relation(ContentFilepath),
2290                                  filename_id = self.tbl_pending_content_associations.c.filename,
2291                                  filename = relation(ContentFilename)))
2292
2293         mapper(Priority, self.tbl_priority,
2294                properties = dict(priority_id = self.tbl_priority.c.id))
2295
2296         mapper(Queue, self.tbl_queue,
2297                properties = dict(queue_id = self.tbl_queue.c.id))
2298
2299         mapper(QueueBuild, self.tbl_queue_build,
2300                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2301                                  queue_id = self.tbl_queue_build.c.queue,
2302                                  queue = relation(Queue, backref='queuebuild')))
2303
2304         mapper(Section, self.tbl_section,
2305                properties = dict(section_id = self.tbl_section.c.id))
2306
2307         mapper(DBSource, self.tbl_source,
2308                properties = dict(source_id = self.tbl_source.c.id,
2309                                  version = self.tbl_source.c.version,
2310                                  maintainer_id = self.tbl_source.c.maintainer,
2311                                  maintainer = relation(Maintainer,
2312                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2313                                  poolfile_id = self.tbl_source.c.file,
2314                                  poolfile = relation(PoolFile),
2315                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2316                                  fingerprint = relation(Fingerprint),
2317                                  changedby_id = self.tbl_source.c.changedby,
2318                                  changedby = relation(Maintainer,
2319                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2320                                  srcfiles = relation(DSCFile,
2321                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2322                                  srcassociations = relation(SrcAssociation,
2323                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2324
2325         mapper(SrcAssociation, self.tbl_src_associations,
2326                properties = dict(sa_id = self.tbl_src_associations.c.id,
2327                                  suite_id = self.tbl_src_associations.c.suite,
2328                                  suite = relation(Suite),
2329                                  source_id = self.tbl_src_associations.c.source,
2330                                  source = relation(DBSource)))
2331
2332         mapper(SrcUploader, self.tbl_src_uploaders,
2333                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2334                                  source_id = self.tbl_src_uploaders.c.source,
2335                                  source = relation(DBSource,
2336                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2337                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2338                                  maintainer = relation(Maintainer,
2339                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2340
2341         mapper(Suite, self.tbl_suite,
2342                properties = dict(suite_id = self.tbl_suite.c.id))
2343
2344         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2345                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2346                                  suite = relation(Suite, backref='suitearchitectures'),
2347                                  arch_id = self.tbl_suite_architectures.c.architecture,
2348                                  architecture = relation(Architecture)))
2349
2350         mapper(Uid, self.tbl_uid,
2351                properties = dict(uid_id = self.tbl_uid.c.id,
2352                                  fingerprint = relation(Fingerprint)))
2353
2354     ## Connection functions
2355     def __createconn(self):
2356         from config import Config
2357         cnf = Config()
2358         if cnf["DB::Host"]:
2359             # TCP/IP
2360             connstr = "postgres://%s" % cnf["DB::Host"]
2361             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2362                 connstr += ":%s" % cnf["DB::Port"]
2363             connstr += "/%s" % cnf["DB::Name"]
2364         else:
2365             # Unix Socket
2366             connstr = "postgres:///%s" % cnf["DB::Name"]
2367             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2368                 connstr += "?port=%s" % cnf["DB::Port"]
2369
2370         self.db_pg   = create_engine(connstr, echo=self.debug)
2371         self.db_meta = MetaData()
2372         self.db_meta.bind = self.db_pg
2373         self.db_smaker = sessionmaker(bind=self.db_pg,
2374                                       autoflush=True,
2375                                       autocommit=False)
2376
2377         self.__setuptables()
2378         self.__setupmappers()
2379
2380     def session(self):
2381         return self.db_smaker()
2382
2383 __all__.append('DBConn')
2384
2385