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