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