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