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