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