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