]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
Merge branch 'merge'
[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 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_queue(queuename, session=None):
1468     """
1469     Returns Queue object for given C{queue name}.
1470
1471     @type queuename: string
1472     @param queuename: The name of the queue
1473
1474     @type session: Session
1475     @param session: Optional SQLA session object (a temporary one will be
1476     generated if not supplied)
1477
1478     @rtype: Queue
1479     @return: Queue object for the given queue
1480     """
1481
1482     q = session.query(Queue).filter_by(queue_name=queuename)
1483
1484     try:
1485         return q.one()
1486     except NoResultFound:
1487         return None
1488
1489 __all__.append('get_queue')
1490
1491 ################################################################################
1492
1493 class QueueBuild(object):
1494     def __init__(self, *args, **kwargs):
1495         pass
1496
1497     def __repr__(self):
1498         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1499
1500 __all__.append('QueueBuild')
1501
1502 @session_wrapper
1503 def get_queue_build(filename, suite, session=None):
1504     """
1505     Returns QueueBuild object for given C{filename} and C{suite}.
1506
1507     @type filename: string
1508     @param filename: The name of the file
1509
1510     @type suiteid: int or str
1511     @param suiteid: Suite name or ID
1512
1513     @type session: Session
1514     @param session: Optional SQLA session object (a temporary one will be
1515     generated if not supplied)
1516
1517     @rtype: Queue
1518     @return: Queue object for the given queue
1519     """
1520
1521     if isinstance(suite, int):
1522         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1523     else:
1524         q = session.query(QueueBuild).filter_by(filename=filename)
1525         q = q.join(Suite).filter_by(suite_name=suite)
1526
1527     try:
1528         return q.one()
1529     except NoResultFound:
1530         return None
1531
1532 __all__.append('get_queue_build')
1533
1534 ################################################################################
1535
1536 class Section(object):
1537     def __init__(self, *args, **kwargs):
1538         pass
1539
1540     def __eq__(self, val):
1541         if isinstance(val, str):
1542             return (self.section == val)
1543         # This signals to use the normal comparison operator
1544         return NotImplemented
1545
1546     def __ne__(self, val):
1547         if isinstance(val, str):
1548             return (self.section != val)
1549         # This signals to use the normal comparison operator
1550         return NotImplemented
1551
1552     def __repr__(self):
1553         return '<Section %s>' % self.section
1554
1555 __all__.append('Section')
1556
1557 @session_wrapper
1558 def get_section(section, session=None):
1559     """
1560     Returns Section object for given C{section name}.
1561
1562     @type section: string
1563     @param section: The name of the section
1564
1565     @type session: Session
1566     @param session: Optional SQLA session object (a temporary one will be
1567     generated if not supplied)
1568
1569     @rtype: Section
1570     @return: Section object for the given section name
1571     """
1572
1573     q = session.query(Section).filter_by(section=section)
1574
1575     try:
1576         return q.one()
1577     except NoResultFound:
1578         return None
1579
1580 __all__.append('get_section')
1581
1582 @session_wrapper
1583 def get_sections(session=None):
1584     """
1585     Returns dictionary of section names -> id mappings
1586
1587     @type session: Session
1588     @param session: Optional SQL session object (a temporary one will be
1589     generated if not supplied)
1590
1591     @rtype: dictionary
1592     @return: dictionary of section names -> id mappings
1593     """
1594
1595     ret = {}
1596     q = session.query(Section)
1597     for x in q.all():
1598         ret[x.section] = x.section_id
1599
1600     return ret
1601
1602 __all__.append('get_sections')
1603
1604 ################################################################################
1605
1606 class DBSource(object):
1607     def __init__(self, *args, **kwargs):
1608         pass
1609
1610     def __repr__(self):
1611         return '<DBSource %s (%s)>' % (self.source, self.version)
1612
1613 __all__.append('DBSource')
1614
1615 @session_wrapper
1616 def source_exists(source, source_version, suites = ["any"], session=None):
1617     """
1618     Ensure that source exists somewhere in the archive for the binary
1619     upload being processed.
1620       1. exact match     => 1.0-3
1621       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1622
1623     @type package: string
1624     @param package: package source name
1625
1626     @type source_version: string
1627     @param source_version: expected source version
1628
1629     @type suites: list
1630     @param suites: list of suites to check in, default I{any}
1631
1632     @type session: Session
1633     @param session: Optional SQLA session object (a temporary one will be
1634     generated if not supplied)
1635
1636     @rtype: int
1637     @return: returns 1 if a source with expected version is found, otherwise 0
1638
1639     """
1640
1641     cnf = Config()
1642     ret = 1
1643
1644     for suite in suites:
1645         q = session.query(DBSource).filter_by(source=source)
1646         if suite != "any":
1647             # source must exist in suite X, or in some other suite that's
1648             # mapped to X, recursively... silent-maps are counted too,
1649             # unreleased-maps aren't.
1650             maps = cnf.ValueList("SuiteMappings")[:]
1651             maps.reverse()
1652             maps = [ m.split() for m in maps ]
1653             maps = [ (x[1], x[2]) for x in maps
1654                             if x[0] == "map" or x[0] == "silent-map" ]
1655             s = [suite]
1656             for x in maps:
1657                 if x[1] in s and x[0] not in s:
1658                     s.append(x[0])
1659
1660             q = q.join(SrcAssociation).join(Suite)
1661             q = q.filter(Suite.suite_name.in_(s))
1662
1663         # Reduce the query results to a list of version numbers
1664         ql = [ j.version for j in q.all() ]
1665
1666         # Try (1)
1667         if source_version in ql:
1668             continue
1669
1670         # Try (2)
1671         from daklib.regexes import re_bin_only_nmu
1672         orig_source_version = re_bin_only_nmu.sub('', source_version)
1673         if orig_source_version in ql:
1674             continue
1675
1676         # No source found so return not ok
1677         ret = 0
1678
1679     return ret
1680
1681 __all__.append('source_exists')
1682
1683 @session_wrapper
1684 def get_suites_source_in(source, session=None):
1685     """
1686     Returns list of Suite objects which given C{source} name is in
1687
1688     @type source: str
1689     @param source: DBSource package name to search for
1690
1691     @rtype: list
1692     @return: list of Suite objects for the given source
1693     """
1694
1695     return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1696
1697 __all__.append('get_suites_source_in')
1698
1699 @session_wrapper
1700 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1701     """
1702     Returns list of DBSource objects for given C{source} name and other parameters
1703
1704     @type source: str
1705     @param source: DBSource package name to search for
1706
1707     @type source: str or None
1708     @param source: DBSource version name to search for or None if not applicable
1709
1710     @type dm_upload_allowed: bool
1711     @param dm_upload_allowed: If None, no effect.  If True or False, only
1712     return packages with that dm_upload_allowed setting
1713
1714     @type session: Session
1715     @param session: Optional SQL session object (a temporary one will be
1716     generated if not supplied)
1717
1718     @rtype: list
1719     @return: list of DBSource objects for the given name (may be empty)
1720     """
1721
1722     q = session.query(DBSource).filter_by(source=source)
1723
1724     if version is not None:
1725         q = q.filter_by(version=version)
1726
1727     if dm_upload_allowed is not None:
1728         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1729
1730     return q.all()
1731
1732 __all__.append('get_sources_from_name')
1733
1734 @session_wrapper
1735 def get_source_in_suite(source, suite, session=None):
1736     """
1737     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1738
1739       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1740       - B{suite} - a suite name, eg. I{unstable}
1741
1742     @type source: string
1743     @param source: source package name
1744
1745     @type suite: string
1746     @param suite: the suite name
1747
1748     @rtype: string
1749     @return: the version for I{source} in I{suite}
1750
1751     """
1752
1753     q = session.query(SrcAssociation)
1754     q = q.join('source').filter_by(source=source)
1755     q = q.join('suite').filter_by(suite_name=suite)
1756
1757     try:
1758         return q.one().source
1759     except NoResultFound:
1760         return None
1761
1762 __all__.append('get_source_in_suite')
1763
1764 ################################################################################
1765
1766 class SrcAssociation(object):
1767     def __init__(self, *args, **kwargs):
1768         pass
1769
1770     def __repr__(self):
1771         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1772
1773 __all__.append('SrcAssociation')
1774
1775 ################################################################################
1776
1777 class SrcFormat(object):
1778     def __init__(self, *args, **kwargs):
1779         pass
1780
1781     def __repr__(self):
1782         return '<SrcFormat %s>' % (self.format_name)
1783
1784 __all__.append('SrcFormat')
1785
1786 ################################################################################
1787
1788 class SrcUploader(object):
1789     def __init__(self, *args, **kwargs):
1790         pass
1791
1792     def __repr__(self):
1793         return '<SrcUploader %s>' % self.uploader_id
1794
1795 __all__.append('SrcUploader')
1796
1797 ################################################################################
1798
1799 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1800                  ('SuiteID', 'suite_id'),
1801                  ('Version', 'version'),
1802                  ('Origin', 'origin'),
1803                  ('Label', 'label'),
1804                  ('Description', 'description'),
1805                  ('Untouchable', 'untouchable'),
1806                  ('Announce', 'announce'),
1807                  ('Codename', 'codename'),
1808                  ('OverrideCodename', 'overridecodename'),
1809                  ('ValidTime', 'validtime'),
1810                  ('Priority', 'priority'),
1811                  ('NotAutomatic', 'notautomatic'),
1812                  ('CopyChanges', 'copychanges'),
1813                  ('CopyDotDak', 'copydotdak'),
1814                  ('CommentsDir', 'commentsdir'),
1815                  ('OverrideSuite', 'overridesuite'),
1816                  ('ChangelogBase', 'changelogbase')]
1817
1818
1819 class Suite(object):
1820     def __init__(self, *args, **kwargs):
1821         pass
1822
1823     def __repr__(self):
1824         return '<Suite %s>' % self.suite_name
1825
1826     def __eq__(self, val):
1827         if isinstance(val, str):
1828             return (self.suite_name == val)
1829         # This signals to use the normal comparison operator
1830         return NotImplemented
1831
1832     def __ne__(self, val):
1833         if isinstance(val, str):
1834             return (self.suite_name != val)
1835         # This signals to use the normal comparison operator
1836         return NotImplemented
1837
1838     def details(self):
1839         ret = []
1840         for disp, field in SUITE_FIELDS:
1841             val = getattr(self, field, None)
1842             if val is not None:
1843                 ret.append("%s: %s" % (disp, val))
1844
1845         return "\n".join(ret)
1846
1847 __all__.append('Suite')
1848
1849 @session_wrapper
1850 def get_suite_architecture(suite, architecture, session=None):
1851     """
1852     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1853     doesn't exist
1854
1855     @type suite: str
1856     @param suite: Suite name to search for
1857
1858     @type architecture: str
1859     @param architecture: Architecture name to search for
1860
1861     @type session: Session
1862     @param session: Optional SQL session object (a temporary one will be
1863     generated if not supplied)
1864
1865     @rtype: SuiteArchitecture
1866     @return: the SuiteArchitecture object or None
1867     """
1868
1869     q = session.query(SuiteArchitecture)
1870     q = q.join(Architecture).filter_by(arch_string=architecture)
1871     q = q.join(Suite).filter_by(suite_name=suite)
1872
1873     try:
1874         return q.one()
1875     except NoResultFound:
1876         return None
1877
1878 __all__.append('get_suite_architecture')
1879
1880 @session_wrapper
1881 def get_suite(suite, session=None):
1882     """
1883     Returns Suite object for given C{suite name}.
1884
1885     @type suite: string
1886     @param suite: The name of the suite
1887
1888     @type session: Session
1889     @param session: Optional SQLA session object (a temporary one will be
1890     generated if not supplied)
1891
1892     @rtype: Suite
1893     @return: Suite object for the requested suite name (None if not present)
1894     """
1895
1896     q = session.query(Suite).filter_by(suite_name=suite)
1897
1898     try:
1899         return q.one()
1900     except NoResultFound:
1901         return None
1902
1903 __all__.append('get_suite')
1904
1905 ################################################################################
1906
1907 class SuiteArchitecture(object):
1908     def __init__(self, *args, **kwargs):
1909         pass
1910
1911     def __repr__(self):
1912         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1913
1914 __all__.append('SuiteArchitecture')
1915
1916 @session_wrapper
1917 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1918     """
1919     Returns list of Architecture objects for given C{suite} name
1920
1921     @type source: str
1922     @param source: Suite name to search for
1923
1924     @type skipsrc: boolean
1925     @param skipsrc: Whether to skip returning the 'source' architecture entry
1926     (Default False)
1927
1928     @type skipall: boolean
1929     @param skipall: Whether to skip returning the 'all' architecture entry
1930     (Default False)
1931
1932     @type session: Session
1933     @param session: Optional SQL session object (a temporary one will be
1934     generated if not supplied)
1935
1936     @rtype: list
1937     @return: list of Architecture objects for the given name (may be empty)
1938     """
1939
1940     q = session.query(Architecture)
1941     q = q.join(SuiteArchitecture)
1942     q = q.join(Suite).filter_by(suite_name=suite)
1943
1944     if skipsrc:
1945         q = q.filter(Architecture.arch_string != 'source')
1946
1947     if skipall:
1948         q = q.filter(Architecture.arch_string != 'all')
1949
1950     q = q.order_by('arch_string')
1951
1952     return q.all()
1953
1954 __all__.append('get_suite_architectures')
1955
1956 ################################################################################
1957
1958 class SuiteSrcFormat(object):
1959     def __init__(self, *args, **kwargs):
1960         pass
1961
1962     def __repr__(self):
1963         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
1964
1965 __all__.append('SuiteSrcFormat')
1966
1967 @session_wrapper
1968 def get_suite_src_formats(suite, session=None):
1969     """
1970     Returns list of allowed SrcFormat for C{suite}.
1971
1972     @type suite: str
1973     @param suite: Suite name to search for
1974
1975     @type session: Session
1976     @param session: Optional SQL session object (a temporary one will be
1977     generated if not supplied)
1978
1979     @rtype: list
1980     @return: the list of allowed source formats for I{suite}
1981     """
1982
1983     q = session.query(SrcFormat)
1984     q = q.join(SuiteSrcFormat)
1985     q = q.join(Suite).filter_by(suite_name=suite)
1986     q = q.order_by('format_name')
1987
1988     return q.all()
1989
1990 __all__.append('get_suite_src_formats')
1991
1992 ################################################################################
1993
1994 class Uid(object):
1995     def __init__(self, *args, **kwargs):
1996         pass
1997
1998     def __eq__(self, val):
1999         if isinstance(val, str):
2000             return (self.uid == val)
2001         # This signals to use the normal comparison operator
2002         return NotImplemented
2003
2004     def __ne__(self, val):
2005         if isinstance(val, str):
2006             return (self.uid != val)
2007         # This signals to use the normal comparison operator
2008         return NotImplemented
2009
2010     def __repr__(self):
2011         return '<Uid %s (%s)>' % (self.uid, self.name)
2012
2013 __all__.append('Uid')
2014
2015 @session_wrapper
2016 def add_database_user(uidname, session=None):
2017     """
2018     Adds a database user
2019
2020     @type uidname: string
2021     @param uidname: The uid of the user to add
2022
2023     @type session: SQLAlchemy
2024     @param session: Optional SQL session object (a temporary one will be
2025     generated if not supplied).  If not passed, a commit will be performed at
2026     the end of the function, otherwise the caller is responsible for commiting.
2027
2028     @rtype: Uid
2029     @return: the uid object for the given uidname
2030     """
2031
2032     session.execute("CREATE USER :uid", {'uid': uidname})
2033     session.commit_or_flush()
2034
2035 __all__.append('add_database_user')
2036
2037 @session_wrapper
2038 def get_or_set_uid(uidname, session=None):
2039     """
2040     Returns uid object for given uidname.
2041
2042     If no matching uidname is found, a row is inserted.
2043
2044     @type uidname: string
2045     @param uidname: The uid 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     q = session.query(Uid).filter_by(uid=uidname)
2057
2058     try:
2059         ret = q.one()
2060     except NoResultFound:
2061         uid = Uid()
2062         uid.uid = uidname
2063         session.add(uid)
2064         session.commit_or_flush()
2065         ret = uid
2066
2067     return ret
2068
2069 __all__.append('get_or_set_uid')
2070
2071 @session_wrapper
2072 def get_uid_from_fingerprint(fpr, session=None):
2073     q = session.query(Uid)
2074     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2075
2076     try:
2077         return q.one()
2078     except NoResultFound:
2079         return None
2080
2081 __all__.append('get_uid_from_fingerprint')
2082
2083 ################################################################################
2084
2085 class DBConn(Singleton):
2086     """
2087     database module init.
2088     """
2089     def __init__(self, *args, **kwargs):
2090         super(DBConn, self).__init__(*args, **kwargs)
2091
2092     def _startup(self, *args, **kwargs):
2093         self.debug = False
2094         if kwargs.has_key('debug'):
2095             self.debug = True
2096         self.__createconn()
2097
2098     def __setuptables(self):
2099         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2100         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2101         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2102         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2103         self.tbl_component = Table('component', self.db_meta, autoload=True)
2104         self.tbl_config = Table('config', self.db_meta, autoload=True)
2105         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2106         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2107         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2108         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2109         self.tbl_files = Table('files', self.db_meta, autoload=True)
2110         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2111         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2112         self.tbl_location = Table('location', self.db_meta, autoload=True)
2113         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2114         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2115         self.tbl_override = Table('override', self.db_meta, autoload=True)
2116         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2117         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2118         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2119         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2120         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2121         self.tbl_section = Table('section', self.db_meta, autoload=True)
2122         self.tbl_source = Table('source', self.db_meta, autoload=True)
2123         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2124         self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2125         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2126         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2127         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2128         self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2129         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2130
2131     def __setupmappers(self):
2132         mapper(Architecture, self.tbl_architecture,
2133                properties = dict(arch_id = self.tbl_architecture.c.id))
2134
2135         mapper(Archive, self.tbl_archive,
2136                properties = dict(archive_id = self.tbl_archive.c.id,
2137                                  archive_name = self.tbl_archive.c.name))
2138
2139         mapper(BinAssociation, self.tbl_bin_associations,
2140                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2141                                  suite_id = self.tbl_bin_associations.c.suite,
2142                                  suite = relation(Suite),
2143                                  binary_id = self.tbl_bin_associations.c.bin,
2144                                  binary = relation(DBBinary)))
2145
2146         mapper(DBBinary, self.tbl_binaries,
2147                properties = dict(binary_id = self.tbl_binaries.c.id,
2148                                  package = self.tbl_binaries.c.package,
2149                                  version = self.tbl_binaries.c.version,
2150                                  maintainer_id = self.tbl_binaries.c.maintainer,
2151                                  maintainer = relation(Maintainer),
2152                                  source_id = self.tbl_binaries.c.source,
2153                                  source = relation(DBSource),
2154                                  arch_id = self.tbl_binaries.c.architecture,
2155                                  architecture = relation(Architecture),
2156                                  poolfile_id = self.tbl_binaries.c.file,
2157                                  poolfile = relation(PoolFile),
2158                                  binarytype = self.tbl_binaries.c.type,
2159                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2160                                  fingerprint = relation(Fingerprint),
2161                                  install_date = self.tbl_binaries.c.install_date,
2162                                  binassociations = relation(BinAssociation,
2163                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2164
2165         mapper(Component, self.tbl_component,
2166                properties = dict(component_id = self.tbl_component.c.id,
2167                                  component_name = self.tbl_component.c.name))
2168
2169         mapper(DBConfig, self.tbl_config,
2170                properties = dict(config_id = self.tbl_config.c.id))
2171
2172         mapper(ContentAssociation, self.tbl_content_associations,
2173                properties = dict(ca_id = self.tbl_content_associations.c.id,
2174                                  filename_id = self.tbl_content_associations.c.filename,
2175                                  filename    = relation(ContentFilename),
2176                                  filepath_id = self.tbl_content_associations.c.filepath,
2177                                  filepath    = relation(ContentFilepath),
2178                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
2179                                  binary      = relation(DBBinary)))
2180
2181
2182         mapper(ContentFilename, self.tbl_content_file_names,
2183                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2184                                  filename = self.tbl_content_file_names.c.file))
2185
2186         mapper(ContentFilepath, self.tbl_content_file_paths,
2187                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2188                                  filepath = self.tbl_content_file_paths.c.path))
2189
2190         mapper(DSCFile, self.tbl_dsc_files,
2191                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2192                                  source_id = self.tbl_dsc_files.c.source,
2193                                  source = relation(DBSource),
2194                                  poolfile_id = self.tbl_dsc_files.c.file,
2195                                  poolfile = relation(PoolFile)))
2196
2197         mapper(PoolFile, self.tbl_files,
2198                properties = dict(file_id = self.tbl_files.c.id,
2199                                  filesize = self.tbl_files.c.size,
2200                                  location_id = self.tbl_files.c.location,
2201                                  location = relation(Location)))
2202
2203         mapper(Fingerprint, self.tbl_fingerprint,
2204                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2205                                  uid_id = self.tbl_fingerprint.c.uid,
2206                                  uid = relation(Uid),
2207                                  keyring_id = self.tbl_fingerprint.c.keyring,
2208                                  keyring = relation(Keyring)))
2209
2210         mapper(Keyring, self.tbl_keyrings,
2211                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2212                                  keyring_id = self.tbl_keyrings.c.id))
2213
2214         mapper(Location, self.tbl_location,
2215                properties = dict(location_id = self.tbl_location.c.id,
2216                                  component_id = self.tbl_location.c.component,
2217                                  component = relation(Component),
2218                                  archive_id = self.tbl_location.c.archive,
2219                                  archive = relation(Archive),
2220                                  archive_type = self.tbl_location.c.type))
2221
2222         mapper(Maintainer, self.tbl_maintainer,
2223                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2224
2225         mapper(NewComment, self.tbl_new_comments,
2226                properties = dict(comment_id = self.tbl_new_comments.c.id))
2227
2228         mapper(Override, self.tbl_override,
2229                properties = dict(suite_id = self.tbl_override.c.suite,
2230                                  suite = relation(Suite),
2231                                  component_id = self.tbl_override.c.component,
2232                                  component = relation(Component),
2233                                  priority_id = self.tbl_override.c.priority,
2234                                  priority = relation(Priority),
2235                                  section_id = self.tbl_override.c.section,
2236                                  section = relation(Section),
2237                                  overridetype_id = self.tbl_override.c.type,
2238                                  overridetype = relation(OverrideType)))
2239
2240         mapper(OverrideType, self.tbl_override_type,
2241                properties = dict(overridetype = self.tbl_override_type.c.type,
2242                                  overridetype_id = self.tbl_override_type.c.id))
2243
2244         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2245                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2246                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
2247                                  filepath = relation(ContentFilepath),
2248                                  filename_id = self.tbl_pending_content_associations.c.filename,
2249                                  filename = relation(ContentFilename)))
2250
2251         mapper(Priority, self.tbl_priority,
2252                properties = dict(priority_id = self.tbl_priority.c.id))
2253
2254         mapper(Queue, self.tbl_queue,
2255                properties = dict(queue_id = self.tbl_queue.c.id))
2256
2257         mapper(QueueBuild, self.tbl_queue_build,
2258                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2259                                  queue_id = self.tbl_queue_build.c.queue,
2260                                  queue = relation(Queue, backref='queuebuild')))
2261
2262         mapper(Section, self.tbl_section,
2263                properties = dict(section_id = self.tbl_section.c.id))
2264
2265         mapper(DBSource, self.tbl_source,
2266                properties = dict(source_id = self.tbl_source.c.id,
2267                                  version = self.tbl_source.c.version,
2268                                  maintainer_id = self.tbl_source.c.maintainer,
2269                                  maintainer = relation(Maintainer,
2270                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2271                                  poolfile_id = self.tbl_source.c.file,
2272                                  poolfile = relation(PoolFile),
2273                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2274                                  fingerprint = relation(Fingerprint),
2275                                  changedby_id = self.tbl_source.c.changedby,
2276                                  changedby = relation(Maintainer,
2277                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2278                                  srcfiles = relation(DSCFile,
2279                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2280                                  srcassociations = relation(SrcAssociation,
2281                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2282
2283         mapper(SrcAssociation, self.tbl_src_associations,
2284                properties = dict(sa_id = self.tbl_src_associations.c.id,
2285                                  suite_id = self.tbl_src_associations.c.suite,
2286                                  suite = relation(Suite),
2287                                  source_id = self.tbl_src_associations.c.source,
2288                                  source = relation(DBSource)))
2289
2290         mapper(SrcFormat, self.tbl_src_format,
2291                properties = dict(src_format_id = self.tbl_src_format.c.id,
2292                                  format_name = self.tbl_src_format.c.format_name))
2293
2294         mapper(SrcUploader, self.tbl_src_uploaders,
2295                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2296                                  source_id = self.tbl_src_uploaders.c.source,
2297                                  source = relation(DBSource,
2298                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2299                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2300                                  maintainer = relation(Maintainer,
2301                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2302
2303         mapper(Suite, self.tbl_suite,
2304                properties = dict(suite_id = self.tbl_suite.c.id))
2305
2306         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2307                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2308                                  suite = relation(Suite, backref='suitearchitectures'),
2309                                  arch_id = self.tbl_suite_architectures.c.architecture,
2310                                  architecture = relation(Architecture)))
2311
2312         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2313                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2314                                  suite = relation(Suite, backref='suitesrcformats'),
2315                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
2316                                  src_format = relation(SrcFormat)))
2317
2318         mapper(Uid, self.tbl_uid,
2319                properties = dict(uid_id = self.tbl_uid.c.id,
2320                                  fingerprint = relation(Fingerprint)))
2321
2322     ## Connection functions
2323     def __createconn(self):
2324         from config import Config
2325         cnf = Config()
2326         if cnf["DB::Host"]:
2327             # TCP/IP
2328             connstr = "postgres://%s" % cnf["DB::Host"]
2329             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2330                 connstr += ":%s" % cnf["DB::Port"]
2331             connstr += "/%s" % cnf["DB::Name"]
2332         else:
2333             # Unix Socket
2334             connstr = "postgres:///%s" % cnf["DB::Name"]
2335             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2336                 connstr += "?port=%s" % cnf["DB::Port"]
2337
2338         self.db_pg   = create_engine(connstr, echo=self.debug)
2339         self.db_meta = MetaData()
2340         self.db_meta.bind = self.db_pg
2341         self.db_smaker = sessionmaker(bind=self.db_pg,
2342                                       autoflush=True,
2343                                       autocommit=False)
2344
2345         self.__setuptables()
2346         self.__setupmappers()
2347
2348     def session(self):
2349         return self.db_smaker()
2350
2351 __all__.append('DBConn')
2352
2353