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