]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
Merge remote branch 'flotow/master'
[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         for fullpath in fullpaths:
615             if fullpath.startswith( './' ):
616                 fullpath = fullpath[2:]
617             
618             session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )", { 'filename': fullpath, 'id': binary_id}  )
619
620         session.commit()
621         if privatetrans:
622             session.close()
623         return True
624
625     except:
626         traceback.print_exc()
627
628         # Only rollback if we set up the session ourself
629         if privatetrans:
630             session.rollback()
631             session.close()
632
633         return False
634
635 __all__.append('insert_content_paths')
636
637 ################################################################################
638
639 class DSCFile(object):
640     def __init__(self, *args, **kwargs):
641         pass
642
643     def __repr__(self):
644         return '<DSCFile %s>' % self.dscfile_id
645
646 __all__.append('DSCFile')
647
648 @session_wrapper
649 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
650     """
651     Returns a list of DSCFiles which may be empty
652
653     @type dscfile_id: int (optional)
654     @param dscfile_id: the dscfile_id of the DSCFiles to find
655
656     @type source_id: int (optional)
657     @param source_id: the source id related to the DSCFiles to find
658
659     @type poolfile_id: int (optional)
660     @param poolfile_id: the poolfile id related to the DSCFiles to find
661
662     @rtype: list
663     @return: Possibly empty list of DSCFiles
664     """
665
666     q = session.query(DSCFile)
667
668     if dscfile_id is not None:
669         q = q.filter_by(dscfile_id=dscfile_id)
670
671     if source_id is not None:
672         q = q.filter_by(source_id=source_id)
673
674     if poolfile_id is not None:
675         q = q.filter_by(poolfile_id=poolfile_id)
676
677     return q.all()
678
679 __all__.append('get_dscfiles')
680
681 ################################################################################
682
683 class PoolFile(object):
684     def __init__(self, *args, **kwargs):
685         pass
686
687     def __repr__(self):
688         return '<PoolFile %s>' % self.filename
689
690 __all__.append('PoolFile')
691
692 @session_wrapper
693 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
694     """
695     Returns a tuple:
696      (ValidFileFound [boolean or None], PoolFile object or None)
697
698     @type filename: string
699     @param filename: the filename of the file to check against the DB
700
701     @type filesize: int
702     @param filesize: the size of the file to check against the DB
703
704     @type md5sum: string
705     @param md5sum: the md5sum of the file to check against the DB
706
707     @type location_id: int
708     @param location_id: the id of the location to look in
709
710     @rtype: tuple
711     @return: Tuple of length 2.
712              If more than one file found with that name:
713                     (None,  None)
714              If valid pool file found: (True, PoolFile object)
715              If valid pool file not found:
716                     (False, None) if no file found
717                     (False, PoolFile object) if file found with size/md5sum mismatch
718     """
719
720     q = session.query(PoolFile).filter_by(filename=filename)
721     q = q.join(Location).filter_by(location_id=location_id)
722
723     ret = None
724
725     if q.count() > 1:
726         ret = (None, None)
727     elif q.count() < 1:
728         ret = (False, None)
729     else:
730         obj = q.one()
731         if obj.md5sum != md5sum or obj.filesize != filesize:
732             ret = (False, obj)
733
734     if ret is None:
735         ret = (True, obj)
736
737     return ret
738
739 __all__.append('check_poolfile')
740
741 @session_wrapper
742 def get_poolfile_by_id(file_id, session=None):
743     """
744     Returns a PoolFile objects or None for the given id
745
746     @type file_id: int
747     @param file_id: the id of the file to look for
748
749     @rtype: PoolFile or None
750     @return: either the PoolFile object or None
751     """
752
753     q = session.query(PoolFile).filter_by(file_id=file_id)
754
755     try:
756         return q.one()
757     except NoResultFound:
758         return None
759
760 __all__.append('get_poolfile_by_id')
761
762
763 @session_wrapper
764 def get_poolfile_by_name(filename, location_id=None, session=None):
765     """
766     Returns an array of PoolFile objects for the given filename and
767     (optionally) location_id
768
769     @type filename: string
770     @param filename: the filename of the file to check against the DB
771
772     @type location_id: int
773     @param location_id: the id of the location to look in (optional)
774
775     @rtype: array
776     @return: array of PoolFile objects
777     """
778
779     q = session.query(PoolFile).filter_by(filename=filename)
780
781     if location_id is not None:
782         q = q.join(Location).filter_by(location_id=location_id)
783
784     return q.all()
785
786 __all__.append('get_poolfile_by_name')
787
788 @session_wrapper
789 def get_poolfile_like_name(filename, session=None):
790     """
791     Returns an array of PoolFile objects which are like the given name
792
793     @type filename: string
794     @param filename: the filename of the file to check against the DB
795
796     @rtype: array
797     @return: array of PoolFile objects
798     """
799
800     # TODO: There must be a way of properly using bind parameters with %FOO%
801     q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
802
803     return q.all()
804
805 __all__.append('get_poolfile_like_name')
806
807 ################################################################################
808
809 class Fingerprint(object):
810     def __init__(self, *args, **kwargs):
811         pass
812
813     def __repr__(self):
814         return '<Fingerprint %s>' % self.fingerprint
815
816 __all__.append('Fingerprint')
817
818 @session_wrapper
819 def get_or_set_fingerprint(fpr, session=None):
820     """
821     Returns Fingerprint object for given fpr.
822
823     If no matching fpr is found, a row is inserted.
824
825     @type fpr: string
826     @param fpr: The fpr to find / add
827
828     @type session: SQLAlchemy
829     @param session: Optional SQL session object (a temporary one will be
830     generated if not supplied).  If not passed, a commit will be performed at
831     the end of the function, otherwise the caller is responsible for commiting.
832     A flush will be performed either way.
833
834     @rtype: Fingerprint
835     @return: the Fingerprint object for the given fpr
836     """
837
838     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
839
840     try:
841         ret = q.one()
842     except NoResultFound:
843         fingerprint = Fingerprint()
844         fingerprint.fingerprint = fpr
845         session.add(fingerprint)
846         session.commit_or_flush()
847         ret = fingerprint
848
849     return ret
850
851 __all__.append('get_or_set_fingerprint')
852
853 ################################################################################
854
855 class Keyring(object):
856     def __init__(self, *args, **kwargs):
857         pass
858
859     def __repr__(self):
860         return '<Keyring %s>' % self.keyring_name
861
862 __all__.append('Keyring')
863
864 @session_wrapper
865 def get_or_set_keyring(keyring, session=None):
866     """
867     If C{keyring} does not have an entry in the C{keyrings} table yet, create one
868     and return the new Keyring
869     If C{keyring} already has an entry, simply return the existing Keyring
870
871     @type keyring: string
872     @param keyring: the keyring name
873
874     @rtype: Keyring
875     @return: the Keyring object for this keyring
876     """
877
878     q = session.query(Keyring).filter_by(keyring_name=keyring)
879
880     try:
881         return q.one()
882     except NoResultFound:
883         obj = Keyring(keyring_name=keyring)
884         session.add(obj)
885         session.commit_or_flush()
886         return obj
887
888 __all__.append('get_or_set_keyring')
889
890 ################################################################################
891
892 class Location(object):
893     def __init__(self, *args, **kwargs):
894         pass
895
896     def __repr__(self):
897         return '<Location %s (%s)>' % (self.path, self.location_id)
898
899 __all__.append('Location')
900
901 @session_wrapper
902 def get_location(location, component=None, archive=None, session=None):
903     """
904     Returns Location object for the given combination of location, component
905     and archive
906
907     @type location: string
908     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
909
910     @type component: string
911     @param component: the component name (if None, no restriction applied)
912
913     @type archive: string
914     @param archive_id: the archive name (if None, no restriction applied)
915
916     @rtype: Location / None
917     @return: Either a Location object or None if one can't be found
918     """
919
920     q = session.query(Location).filter_by(path=location)
921
922     if archive is not None:
923         q = q.join(Archive).filter_by(archive_name=archive)
924
925     if component is not None:
926         q = q.join(Component).filter_by(component_name=component)
927
928     try:
929         return q.one()
930     except NoResultFound:
931         return None
932
933 __all__.append('get_location')
934
935 ################################################################################
936
937 class Maintainer(object):
938     def __init__(self, *args, **kwargs):
939         pass
940
941     def __repr__(self):
942         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
943
944     def get_split_maintainer(self):
945         if not hasattr(self, 'name') or self.name is None:
946             return ('', '', '', '')
947
948         return fix_maintainer(self.name.strip())
949
950 __all__.append('Maintainer')
951
952 @session_wrapper
953 def get_or_set_maintainer(name, session=None):
954     """
955     Returns Maintainer object for given maintainer name.
956
957     If no matching maintainer name is found, a row is inserted.
958
959     @type name: string
960     @param name: The maintainer name to add
961
962     @type session: SQLAlchemy
963     @param session: Optional SQL session object (a temporary one will be
964     generated if not supplied).  If not passed, a commit will be performed at
965     the end of the function, otherwise the caller is responsible for commiting.
966     A flush will be performed either way.
967
968     @rtype: Maintainer
969     @return: the Maintainer object for the given maintainer
970     """
971
972     q = session.query(Maintainer).filter_by(name=name)
973     try:
974         ret = q.one()
975     except NoResultFound:
976         maintainer = Maintainer()
977         maintainer.name = name
978         session.add(maintainer)
979         session.commit_or_flush()
980         ret = maintainer
981
982     return ret
983
984 __all__.append('get_or_set_maintainer')
985
986 @session_wrapper
987 def get_maintainer(maintainer_id, session=None):
988     """
989     Return the name of the maintainer behind C{maintainer_id} or None if that
990     maintainer_id is invalid.
991
992     @type maintainer_id: int
993     @param maintainer_id: the id of the maintainer
994
995     @rtype: Maintainer
996     @return: the Maintainer with this C{maintainer_id}
997     """
998
999     return session.query(Maintainer).get(maintainer_id)
1000
1001 __all__.append('get_maintainer')
1002
1003 ################################################################################
1004
1005 class NewComment(object):
1006     def __init__(self, *args, **kwargs):
1007         pass
1008
1009     def __repr__(self):
1010         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1011
1012 __all__.append('NewComment')
1013
1014 @session_wrapper
1015 def has_new_comment(package, version, session=None):
1016     """
1017     Returns true if the given combination of C{package}, C{version} has a comment.
1018
1019     @type package: string
1020     @param package: name of the package
1021
1022     @type version: string
1023     @param version: package version
1024
1025     @type session: Session
1026     @param session: Optional SQLA session object (a temporary one will be
1027     generated if not supplied)
1028
1029     @rtype: boolean
1030     @return: true/false
1031     """
1032
1033     q = session.query(NewComment)
1034     q = q.filter_by(package=package)
1035     q = q.filter_by(version=version)
1036
1037     return bool(q.count() > 0)
1038
1039 __all__.append('has_new_comment')
1040
1041 @session_wrapper
1042 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1043     """
1044     Returns (possibly empty) list of NewComment objects for the given
1045     parameters
1046
1047     @type package: string (optional)
1048     @param package: name of the package
1049
1050     @type version: string (optional)
1051     @param version: package version
1052
1053     @type comment_id: int (optional)
1054     @param comment_id: An id of a comment
1055
1056     @type session: Session
1057     @param session: Optional SQLA session object (a temporary one will be
1058     generated if not supplied)
1059
1060     @rtype: list
1061     @return: A (possibly empty) list of NewComment objects will be returned
1062     """
1063
1064     q = session.query(NewComment)
1065     if package is not None: q = q.filter_by(package=package)
1066     if version is not None: q = q.filter_by(version=version)
1067     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1068
1069     return q.all()
1070
1071 __all__.append('get_new_comments')
1072
1073 ################################################################################
1074
1075 class Override(object):
1076     def __init__(self, *args, **kwargs):
1077         pass
1078
1079     def __repr__(self):
1080         return '<Override %s (%s)>' % (self.package, self.suite_id)
1081
1082 __all__.append('Override')
1083
1084 @session_wrapper
1085 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1086     """
1087     Returns Override object for the given parameters
1088
1089     @type package: string
1090     @param package: The name of the package
1091
1092     @type suite: string, list or None
1093     @param suite: The name of the suite (or suites if a list) to limit to.  If
1094                   None, don't limit.  Defaults to None.
1095
1096     @type component: string, list or None
1097     @param component: The name of the component (or components if a list) to
1098                       limit to.  If None, don't limit.  Defaults to None.
1099
1100     @type overridetype: string, list or None
1101     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1102                          limit to.  If None, don't limit.  Defaults to None.
1103
1104     @type session: Session
1105     @param session: Optional SQLA session object (a temporary one will be
1106     generated if not supplied)
1107
1108     @rtype: list
1109     @return: A (possibly empty) list of Override objects will be returned
1110     """
1111
1112     q = session.query(Override)
1113     q = q.filter_by(package=package)
1114
1115     if suite is not None:
1116         if not isinstance(suite, list): suite = [suite]
1117         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1118
1119     if component is not None:
1120         if not isinstance(component, list): component = [component]
1121         q = q.join(Component).filter(Component.component_name.in_(component))
1122
1123     if overridetype is not None:
1124         if not isinstance(overridetype, list): overridetype = [overridetype]
1125         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1126
1127     return q.all()
1128
1129 __all__.append('get_override')
1130
1131
1132 ################################################################################
1133
1134 class OverrideType(object):
1135     def __init__(self, *args, **kwargs):
1136         pass
1137
1138     def __repr__(self):
1139         return '<OverrideType %s>' % self.overridetype
1140
1141 __all__.append('OverrideType')
1142
1143 @session_wrapper
1144 def get_override_type(override_type, session=None):
1145     """
1146     Returns OverrideType object for given C{override type}.
1147
1148     @type override_type: string
1149     @param override_type: The name of the override type
1150
1151     @type session: Session
1152     @param session: Optional SQLA session object (a temporary one will be
1153     generated if not supplied)
1154
1155     @rtype: int
1156     @return: the database id for the given override type
1157     """
1158
1159     q = session.query(OverrideType).filter_by(overridetype=override_type)
1160
1161     try:
1162         return q.one()
1163     except NoResultFound:
1164         return None
1165
1166 __all__.append('get_override_type')
1167
1168 ################################################################################
1169
1170 class PendingContentAssociation(object):
1171     def __init__(self, *args, **kwargs):
1172         pass
1173
1174     def __repr__(self):
1175         return '<PendingContentAssociation %s>' % self.pca_id
1176
1177 __all__.append('PendingContentAssociation')
1178
1179 def insert_pending_content_paths(package, fullpaths, session=None):
1180     """
1181     Make sure given paths are temporarily associated with given
1182     package
1183
1184     @type package: dict
1185     @param package: the package to associate with should have been read in from the binary control file
1186     @type fullpaths: list
1187     @param fullpaths: the list of paths of the file being associated with the binary
1188     @type session: SQLAlchemy session
1189     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1190     is responsible for ensuring a transaction has begun and committing the
1191     results or rolling back based on the result code.  If not passed, a commit
1192     will be performed at the end of the function
1193
1194     @return: True upon success, False if there is a problem
1195     """
1196
1197     privatetrans = False
1198
1199     if session is None:
1200         session = DBConn().session()
1201         privatetrans = True
1202
1203     try:
1204         arch = get_architecture(package['Architecture'], session)
1205         arch_id = arch.arch_id
1206
1207         # Remove any already existing recorded files for this package
1208         q = session.query(PendingContentAssociation)
1209         q = q.filter_by(package=package['Package'])
1210         q = q.filter_by(version=package['Version'])
1211         q = q.filter_by(architecture=arch_id)
1212         q.delete()
1213
1214         # Insert paths
1215         pathcache = {}
1216         for fullpath in fullpaths:
1217             (path, file) = os.path.split(fullpath)
1218
1219             if path.startswith( "./" ):
1220                 path = path[2:]
1221
1222             filepath_id = get_or_set_contents_path_id(path, session)
1223             filename_id = get_or_set_contents_file_id(file, session)
1224
1225             pathcache[fullpath] = (filepath_id, filename_id)
1226
1227         for fullpath, dat in pathcache.items():
1228             pca = PendingContentAssociation()
1229             pca.package = package['Package']
1230             pca.version = package['Version']
1231             pca.filepath_id = dat[0]
1232             pca.filename_id = dat[1]
1233             pca.architecture = arch_id
1234             session.add(pca)
1235
1236         # Only commit if we set up the session ourself
1237         if privatetrans:
1238             session.commit()
1239             session.close()
1240         else:
1241             session.flush()
1242
1243         return True
1244     except Exception, e:
1245         traceback.print_exc()
1246
1247         # Only rollback if we set up the session ourself
1248         if privatetrans:
1249             session.rollback()
1250             session.close()
1251
1252         return False
1253
1254 __all__.append('insert_pending_content_paths')
1255
1256 ################################################################################
1257
1258 class Priority(object):
1259     def __init__(self, *args, **kwargs):
1260         pass
1261
1262     def __eq__(self, val):
1263         if isinstance(val, str):
1264             return (self.priority == val)
1265         # This signals to use the normal comparison operator
1266         return NotImplemented
1267
1268     def __ne__(self, val):
1269         if isinstance(val, str):
1270             return (self.priority != val)
1271         # This signals to use the normal comparison operator
1272         return NotImplemented
1273
1274     def __repr__(self):
1275         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1276
1277 __all__.append('Priority')
1278
1279 @session_wrapper
1280 def get_priority(priority, session=None):
1281     """
1282     Returns Priority object for given C{priority name}.
1283
1284     @type priority: string
1285     @param priority: The name of the priority
1286
1287     @type session: Session
1288     @param session: Optional SQLA session object (a temporary one will be
1289     generated if not supplied)
1290
1291     @rtype: Priority
1292     @return: Priority object for the given priority
1293     """
1294
1295     q = session.query(Priority).filter_by(priority=priority)
1296
1297     try:
1298         return q.one()
1299     except NoResultFound:
1300         return None
1301
1302 __all__.append('get_priority')
1303
1304 @session_wrapper
1305 def get_priorities(session=None):
1306     """
1307     Returns dictionary of priority names -> id mappings
1308
1309     @type session: Session
1310     @param session: Optional SQL session object (a temporary one will be
1311     generated if not supplied)
1312
1313     @rtype: dictionary
1314     @return: dictionary of priority names -> id mappings
1315     """
1316
1317     ret = {}
1318     q = session.query(Priority)
1319     for x in q.all():
1320         ret[x.priority] = x.priority_id
1321
1322     return ret
1323
1324 __all__.append('get_priorities')
1325
1326 ################################################################################
1327
1328 class Queue(object):
1329     def __init__(self, *args, **kwargs):
1330         pass
1331
1332     def __repr__(self):
1333         return '<Queue %s>' % self.queue_name
1334
1335     def autobuild_upload(self, changes, srcpath, session=None):
1336         """
1337         Update queue_build database table used for incoming autobuild support.
1338
1339         @type changes: Changes
1340         @param changes: changes object for the upload to process
1341
1342         @type srcpath: string
1343         @param srcpath: path for the queue file entries/link destinations
1344
1345         @type session: SQLAlchemy session
1346         @param session: Optional SQLAlchemy session.  If this is passed, the
1347         caller is responsible for ensuring a transaction has begun and
1348         committing the results or rolling back based on the result code.  If
1349         not passed, a commit will be performed at the end of the function,
1350         otherwise the caller is responsible for commiting.
1351
1352         @rtype: NoneType or string
1353         @return: None if the operation failed, a string describing the error if not
1354         """
1355
1356         privatetrans = False
1357         if session is None:
1358             session = DBConn().session()
1359             privatetrans = True
1360
1361         # TODO: Remove by moving queue config into the database
1362         conf = Config()
1363
1364         for suitename in changes.changes["distribution"].keys():
1365             # TODO: Move into database as:
1366             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1367             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1368             #       This also gets rid of the SecurityQueueBuild hack below
1369             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1370                 continue
1371
1372             # Find suite object
1373             s = get_suite(suitename, session)
1374             if s is None:
1375                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1376
1377             # TODO: Get from database as above
1378             dest_dir = conf["Dir::QueueBuild"]
1379
1380             # TODO: Move into database as above
1381             if conf.FindB("Dinstall::SecurityQueueBuild"):
1382                 dest_dir = os.path.join(dest_dir, suitename)
1383
1384             for file_entry in changes.files.keys():
1385                 src = os.path.join(srcpath, file_entry)
1386                 dest = os.path.join(dest_dir, file_entry)
1387
1388                 # TODO: Move into database as above
1389                 if conf.FindB("Dinstall::SecurityQueueBuild"):
1390                     # Copy it since the original won't be readable by www-data
1391                     import utils
1392                     utils.copy(src, dest)
1393                 else:
1394                     # Create a symlink to it
1395                     os.symlink(src, dest)
1396
1397                 qb = QueueBuild()
1398                 qb.suite_id = s.suite_id
1399                 qb.queue_id = self.queue_id
1400                 qb.filename = dest
1401                 qb.in_queue = True
1402
1403                 session.add(qb)
1404
1405             exists, symlinked = utils.ensure_orig_files(changes, dest, session)
1406
1407             # Add symlinked files to the list of packages for later processing
1408             # by apt-ftparchive
1409             for filename in symlinked:
1410                 qb = QueueBuild()
1411                 qb.suite_id = s.suite_id
1412                 qb.queue_id = self.queue_id
1413                 qb.filename = filename
1414                 qb.in_queue = True
1415                 session.add(qb)
1416
1417             # Update files to ensure they are not removed prematurely
1418             for filename in exists:
1419                 qb = get_queue_build(filename, s.suite_id, session)
1420                 if qb is None:
1421                     qb.in_queue = True
1422                     qb.last_used = None
1423                     session.add(qb)
1424
1425         if privatetrans:
1426             session.commit()
1427             session.close()
1428
1429         return None
1430
1431 __all__.append('Queue')
1432
1433 @session_wrapper
1434 def get_or_set_queue(queuename, session=None):
1435     """
1436     Returns Queue object for given C{queue name}, creating it if it does not
1437     exist.
1438
1439     @type queuename: string
1440     @param queuename: The name of the queue
1441
1442     @type session: Session
1443     @param session: Optional SQLA session object (a temporary one will be
1444     generated if not supplied)
1445
1446     @rtype: Queue
1447     @return: Queue object for the given queue
1448     """
1449
1450     q = session.query(Queue).filter_by(queue_name=queuename)
1451
1452     try:
1453         ret = q.one()
1454     except NoResultFound:
1455         queue = Queue()
1456         queue.queue_name = queuename
1457         session.add(queue)
1458         session.commit_or_flush()
1459         ret = queue
1460
1461     return ret
1462
1463 __all__.append('get_or_set_queue')
1464
1465 ################################################################################
1466
1467 class QueueBuild(object):
1468     def __init__(self, *args, **kwargs):
1469         pass
1470
1471     def __repr__(self):
1472         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1473
1474 __all__.append('QueueBuild')
1475
1476 @session_wrapper
1477 def get_queue_build(filename, suite, session=None):
1478     """
1479     Returns QueueBuild object for given C{filename} and C{suite}.
1480
1481     @type filename: string
1482     @param filename: The name of the file
1483
1484     @type suiteid: int or str
1485     @param suiteid: Suite name or ID
1486
1487     @type session: Session
1488     @param session: Optional SQLA session object (a temporary one will be
1489     generated if not supplied)
1490
1491     @rtype: Queue
1492     @return: Queue object for the given queue
1493     """
1494
1495     if isinstance(suite, int):
1496         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1497     else:
1498         q = session.query(QueueBuild).filter_by(filename=filename)
1499         q = q.join(Suite).filter_by(suite_name=suite)
1500
1501     try:
1502         return q.one()
1503     except NoResultFound:
1504         return None
1505
1506 __all__.append('get_queue_build')
1507
1508 ################################################################################
1509
1510 class Section(object):
1511     def __init__(self, *args, **kwargs):
1512         pass
1513
1514     def __eq__(self, val):
1515         if isinstance(val, str):
1516             return (self.section == val)
1517         # This signals to use the normal comparison operator
1518         return NotImplemented
1519
1520     def __ne__(self, val):
1521         if isinstance(val, str):
1522             return (self.section != val)
1523         # This signals to use the normal comparison operator
1524         return NotImplemented
1525
1526     def __repr__(self):
1527         return '<Section %s>' % self.section
1528
1529 __all__.append('Section')
1530
1531 @session_wrapper
1532 def get_section(section, session=None):
1533     """
1534     Returns Section object for given C{section name}.
1535
1536     @type section: string
1537     @param section: The name of the section
1538
1539     @type session: Session
1540     @param session: Optional SQLA session object (a temporary one will be
1541     generated if not supplied)
1542
1543     @rtype: Section
1544     @return: Section object for the given section name
1545     """
1546
1547     q = session.query(Section).filter_by(section=section)
1548
1549     try:
1550         return q.one()
1551     except NoResultFound:
1552         return None
1553
1554 __all__.append('get_section')
1555
1556 @session_wrapper
1557 def get_sections(session=None):
1558     """
1559     Returns dictionary of section names -> id mappings
1560
1561     @type session: Session
1562     @param session: Optional SQL session object (a temporary one will be
1563     generated if not supplied)
1564
1565     @rtype: dictionary
1566     @return: dictionary of section names -> id mappings
1567     """
1568
1569     ret = {}
1570     q = session.query(Section)
1571     for x in q.all():
1572         ret[x.section] = x.section_id
1573
1574     return ret
1575
1576 __all__.append('get_sections')
1577
1578 ################################################################################
1579
1580 class DBSource(object):
1581     def __init__(self, *args, **kwargs):
1582         pass
1583
1584     def __repr__(self):
1585         return '<DBSource %s (%s)>' % (self.source, self.version)
1586
1587 __all__.append('DBSource')
1588
1589 @session_wrapper
1590 def source_exists(source, source_version, suites = ["any"], session=None):
1591     """
1592     Ensure that source exists somewhere in the archive for the binary
1593     upload being processed.
1594       1. exact match     => 1.0-3
1595       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1596
1597     @type package: string
1598     @param package: package source name
1599
1600     @type source_version: string
1601     @param source_version: expected source version
1602
1603     @type suites: list
1604     @param suites: list of suites to check in, default I{any}
1605
1606     @type session: Session
1607     @param session: Optional SQLA session object (a temporary one will be
1608     generated if not supplied)
1609
1610     @rtype: int
1611     @return: returns 1 if a source with expected version is found, otherwise 0
1612
1613     """
1614
1615     cnf = Config()
1616     ret = 1
1617
1618     for suite in suites:
1619         q = session.query(DBSource).filter_by(source=source)
1620         if suite != "any":
1621             # source must exist in suite X, or in some other suite that's
1622             # mapped to X, recursively... silent-maps are counted too,
1623             # unreleased-maps aren't.
1624             maps = cnf.ValueList("SuiteMappings")[:]
1625             maps.reverse()
1626             maps = [ m.split() for m in maps ]
1627             maps = [ (x[1], x[2]) for x in maps
1628                             if x[0] == "map" or x[0] == "silent-map" ]
1629             s = [suite]
1630             for x in maps:
1631                 if x[1] in s and x[0] not in s:
1632                     s.append(x[0])
1633
1634             q = q.join(SrcAssociation).join(Suite)
1635             q = q.filter(Suite.suite_name.in_(s))
1636
1637         # Reduce the query results to a list of version numbers
1638         ql = [ j.version for j in q.all() ]
1639
1640         # Try (1)
1641         if source_version in ql:
1642             continue
1643
1644         # Try (2)
1645         from daklib.regexes import re_bin_only_nmu
1646         orig_source_version = re_bin_only_nmu.sub('', source_version)
1647         if orig_source_version in ql:
1648             continue
1649
1650         # No source found so return not ok
1651         ret = 0
1652
1653     return ret
1654
1655 __all__.append('source_exists')
1656
1657 @session_wrapper
1658 def get_suites_source_in(source, session=None):
1659     """
1660     Returns list of Suite objects which given C{source} name is in
1661
1662     @type source: str
1663     @param source: DBSource package name to search for
1664
1665     @rtype: list
1666     @return: list of Suite objects for the given source
1667     """
1668
1669     return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1670
1671 __all__.append('get_suites_source_in')
1672
1673 @session_wrapper
1674 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1675     """
1676     Returns list of DBSource objects for given C{source} name and other parameters
1677
1678     @type source: str
1679     @param source: DBSource package name to search for
1680
1681     @type source: str or None
1682     @param source: DBSource version name to search for or None if not applicable
1683
1684     @type dm_upload_allowed: bool
1685     @param dm_upload_allowed: If None, no effect.  If True or False, only
1686     return packages with that dm_upload_allowed setting
1687
1688     @type session: Session
1689     @param session: Optional SQL session object (a temporary one will be
1690     generated if not supplied)
1691
1692     @rtype: list
1693     @return: list of DBSource objects for the given name (may be empty)
1694     """
1695
1696     q = session.query(DBSource).filter_by(source=source)
1697
1698     if version is not None:
1699         q = q.filter_by(version=version)
1700
1701     if dm_upload_allowed is not None:
1702         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1703
1704     return q.all()
1705
1706 __all__.append('get_sources_from_name')
1707
1708 @session_wrapper
1709 def get_source_in_suite(source, suite, session=None):
1710     """
1711     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1712
1713       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1714       - B{suite} - a suite name, eg. I{unstable}
1715
1716     @type source: string
1717     @param source: source package name
1718
1719     @type suite: string
1720     @param suite: the suite name
1721
1722     @rtype: string
1723     @return: the version for I{source} in I{suite}
1724
1725     """
1726
1727     q = session.query(SrcAssociation)
1728     q = q.join('source').filter_by(source=source)
1729     q = q.join('suite').filter_by(suite_name=suite)
1730
1731     try:
1732         return q.one().source
1733     except NoResultFound:
1734         return None
1735
1736 __all__.append('get_source_in_suite')
1737
1738 ################################################################################
1739
1740 class SrcAssociation(object):
1741     def __init__(self, *args, **kwargs):
1742         pass
1743
1744     def __repr__(self):
1745         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1746
1747 __all__.append('SrcAssociation')
1748
1749 ################################################################################
1750
1751 class SrcFormat(object):
1752     def __init__(self, *args, **kwargs):
1753         pass
1754
1755     def __repr__(self):
1756         return '<SrcFormat %s>' % (self.format_name)
1757
1758 __all__.append('SrcFormat')
1759
1760 ################################################################################
1761
1762 class SrcUploader(object):
1763     def __init__(self, *args, **kwargs):
1764         pass
1765
1766     def __repr__(self):
1767         return '<SrcUploader %s>' % self.uploader_id
1768
1769 __all__.append('SrcUploader')
1770
1771 ################################################################################
1772
1773 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1774                  ('SuiteID', 'suite_id'),
1775                  ('Version', 'version'),
1776                  ('Origin', 'origin'),
1777                  ('Label', 'label'),
1778                  ('Description', 'description'),
1779                  ('Untouchable', 'untouchable'),
1780                  ('Announce', 'announce'),
1781                  ('Codename', 'codename'),
1782                  ('OverrideCodename', 'overridecodename'),
1783                  ('ValidTime', 'validtime'),
1784                  ('Priority', 'priority'),
1785                  ('NotAutomatic', 'notautomatic'),
1786                  ('CopyChanges', 'copychanges'),
1787                  ('CopyDotDak', 'copydotdak'),
1788                  ('CommentsDir', 'commentsdir'),
1789                  ('OverrideSuite', 'overridesuite'),
1790                  ('ChangelogBase', 'changelogbase')]
1791
1792
1793 class Suite(object):
1794     def __init__(self, *args, **kwargs):
1795         pass
1796
1797     def __repr__(self):
1798         return '<Suite %s>' % self.suite_name
1799
1800     def __eq__(self, val):
1801         if isinstance(val, str):
1802             return (self.suite_name == val)
1803         # This signals to use the normal comparison operator
1804         return NotImplemented
1805
1806     def __ne__(self, val):
1807         if isinstance(val, str):
1808             return (self.suite_name != val)
1809         # This signals to use the normal comparison operator
1810         return NotImplemented
1811
1812     def details(self):
1813         ret = []
1814         for disp, field in SUITE_FIELDS:
1815             val = getattr(self, field, None)
1816             if val is not None:
1817                 ret.append("%s: %s" % (disp, val))
1818
1819         return "\n".join(ret)
1820
1821 __all__.append('Suite')
1822
1823 @session_wrapper
1824 def get_suite_architecture(suite, architecture, session=None):
1825     """
1826     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1827     doesn't exist
1828
1829     @type suite: str
1830     @param suite: Suite name to search for
1831
1832     @type architecture: str
1833     @param architecture: Architecture name to search for
1834
1835     @type session: Session
1836     @param session: Optional SQL session object (a temporary one will be
1837     generated if not supplied)
1838
1839     @rtype: SuiteArchitecture
1840     @return: the SuiteArchitecture object or None
1841     """
1842
1843     q = session.query(SuiteArchitecture)
1844     q = q.join(Architecture).filter_by(arch_string=architecture)
1845     q = q.join(Suite).filter_by(suite_name=suite)
1846
1847     try:
1848         return q.one()
1849     except NoResultFound:
1850         return None
1851
1852 __all__.append('get_suite_architecture')
1853
1854 @session_wrapper
1855 def get_suite(suite, session=None):
1856     """
1857     Returns Suite object for given C{suite name}.
1858
1859     @type suite: string
1860     @param suite: The name of the suite
1861
1862     @type session: Session
1863     @param session: Optional SQLA session object (a temporary one will be
1864     generated if not supplied)
1865
1866     @rtype: Suite
1867     @return: Suite object for the requested suite name (None if not present)
1868     """
1869
1870     q = session.query(Suite).filter_by(suite_name=suite)
1871
1872     try:
1873         return q.one()
1874     except NoResultFound:
1875         return None
1876
1877 __all__.append('get_suite')
1878
1879 ################################################################################
1880
1881 class SuiteArchitecture(object):
1882     def __init__(self, *args, **kwargs):
1883         pass
1884
1885     def __repr__(self):
1886         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1887
1888 __all__.append('SuiteArchitecture')
1889
1890 @session_wrapper
1891 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1892     """
1893     Returns list of Architecture objects for given C{suite} name
1894
1895     @type source: str
1896     @param source: Suite name to search for
1897
1898     @type skipsrc: boolean
1899     @param skipsrc: Whether to skip returning the 'source' architecture entry
1900     (Default False)
1901
1902     @type skipall: boolean
1903     @param skipall: Whether to skip returning the 'all' architecture entry
1904     (Default False)
1905
1906     @type session: Session
1907     @param session: Optional SQL session object (a temporary one will be
1908     generated if not supplied)
1909
1910     @rtype: list
1911     @return: list of Architecture objects for the given name (may be empty)
1912     """
1913
1914     q = session.query(Architecture)
1915     q = q.join(SuiteArchitecture)
1916     q = q.join(Suite).filter_by(suite_name=suite)
1917
1918     if skipsrc:
1919         q = q.filter(Architecture.arch_string != 'source')
1920
1921     if skipall:
1922         q = q.filter(Architecture.arch_string != 'all')
1923
1924     q = q.order_by('arch_string')
1925
1926     return q.all()
1927
1928 __all__.append('get_suite_architectures')
1929
1930 ################################################################################
1931
1932 class SuiteSrcFormat(object):
1933     def __init__(self, *args, **kwargs):
1934         pass
1935
1936     def __repr__(self):
1937         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
1938
1939 __all__.append('SuiteSrcFormat')
1940
1941 @session_wrapper
1942 def get_suite_src_formats(suite, session=None):
1943     """
1944     Returns list of allowed SrcFormat for C{suite}.
1945
1946     @type suite: str
1947     @param suite: Suite name to search for
1948
1949     @type session: Session
1950     @param session: Optional SQL session object (a temporary one will be
1951     generated if not supplied)
1952
1953     @rtype: list
1954     @return: the list of allowed source formats for I{suite}
1955     """
1956
1957     q = session.query(SrcFormat)
1958     q = q.join(SuiteSrcFormat)
1959     q = q.join(Suite).filter_by(suite_name=suite)
1960     q = q.order_by('format_name')
1961
1962     return q.all()
1963
1964 __all__.append('get_suite_src_formats')
1965
1966 ################################################################################
1967
1968 class Uid(object):
1969     def __init__(self, *args, **kwargs):
1970         pass
1971
1972     def __eq__(self, val):
1973         if isinstance(val, str):
1974             return (self.uid == val)
1975         # This signals to use the normal comparison operator
1976         return NotImplemented
1977
1978     def __ne__(self, val):
1979         if isinstance(val, str):
1980             return (self.uid != val)
1981         # This signals to use the normal comparison operator
1982         return NotImplemented
1983
1984     def __repr__(self):
1985         return '<Uid %s (%s)>' % (self.uid, self.name)
1986
1987 __all__.append('Uid')
1988
1989 @session_wrapper
1990 def add_database_user(uidname, session=None):
1991     """
1992     Adds a database user
1993
1994     @type uidname: string
1995     @param uidname: The uid of the user to add
1996
1997     @type session: SQLAlchemy
1998     @param session: Optional SQL session object (a temporary one will be
1999     generated if not supplied).  If not passed, a commit will be performed at
2000     the end of the function, otherwise the caller is responsible for commiting.
2001
2002     @rtype: Uid
2003     @return: the uid object for the given uidname
2004     """
2005
2006     session.execute("CREATE USER :uid", {'uid': uidname})
2007     session.commit_or_flush()
2008
2009 __all__.append('add_database_user')
2010
2011 @session_wrapper
2012 def get_or_set_uid(uidname, session=None):
2013     """
2014     Returns uid object for given uidname.
2015
2016     If no matching uidname is found, a row is inserted.
2017
2018     @type uidname: string
2019     @param uidname: The uid to add
2020
2021     @type session: SQLAlchemy
2022     @param session: Optional SQL session object (a temporary one will be
2023     generated if not supplied).  If not passed, a commit will be performed at
2024     the end of the function, otherwise the caller is responsible for commiting.
2025
2026     @rtype: Uid
2027     @return: the uid object for the given uidname
2028     """
2029
2030     q = session.query(Uid).filter_by(uid=uidname)
2031
2032     try:
2033         ret = q.one()
2034     except NoResultFound:
2035         uid = Uid()
2036         uid.uid = uidname
2037         session.add(uid)
2038         session.commit_or_flush()
2039         ret = uid
2040
2041     return ret
2042
2043 __all__.append('get_or_set_uid')
2044
2045 @session_wrapper
2046 def get_uid_from_fingerprint(fpr, session=None):
2047     q = session.query(Uid)
2048     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2049
2050     try:
2051         return q.one()
2052     except NoResultFound:
2053         return None
2054
2055 __all__.append('get_uid_from_fingerprint')
2056
2057 ################################################################################
2058
2059 class DBConn(Singleton):
2060     """
2061     database module init.
2062     """
2063     def __init__(self, *args, **kwargs):
2064         super(DBConn, self).__init__(*args, **kwargs)
2065
2066     def _startup(self, *args, **kwargs):
2067         self.debug = False
2068         if kwargs.has_key('debug'):
2069             self.debug = True
2070         self.__createconn()
2071
2072     def __setuptables(self):
2073         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2074         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2075         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2076         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2077         self.tbl_component = Table('component', self.db_meta, autoload=True)
2078         self.tbl_config = Table('config', self.db_meta, autoload=True)
2079         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2080         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2081         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2082         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2083         self.tbl_files = Table('files', self.db_meta, autoload=True)
2084         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2085         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2086         self.tbl_location = Table('location', self.db_meta, autoload=True)
2087         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2088         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2089         self.tbl_override = Table('override', self.db_meta, autoload=True)
2090         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2091         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2092         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2093         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2094         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2095         self.tbl_section = Table('section', self.db_meta, autoload=True)
2096         self.tbl_source = Table('source', self.db_meta, autoload=True)
2097         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2098         self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2099         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2100         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2101         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2102         self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2103         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2104
2105     def __setupmappers(self):
2106         mapper(Architecture, self.tbl_architecture,
2107                properties = dict(arch_id = self.tbl_architecture.c.id))
2108
2109         mapper(Archive, self.tbl_archive,
2110                properties = dict(archive_id = self.tbl_archive.c.id,
2111                                  archive_name = self.tbl_archive.c.name))
2112
2113         mapper(BinAssociation, self.tbl_bin_associations,
2114                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2115                                  suite_id = self.tbl_bin_associations.c.suite,
2116                                  suite = relation(Suite),
2117                                  binary_id = self.tbl_bin_associations.c.bin,
2118                                  binary = relation(DBBinary)))
2119
2120
2121         mapper(DBBinary, self.tbl_binaries,
2122                properties = dict(binary_id = self.tbl_binaries.c.id,
2123                                  package = self.tbl_binaries.c.package,
2124                                  version = self.tbl_binaries.c.version,
2125                                  maintainer_id = self.tbl_binaries.c.maintainer,
2126                                  maintainer = relation(Maintainer),
2127                                  source_id = self.tbl_binaries.c.source,
2128                                  source = relation(DBSource),
2129                                  arch_id = self.tbl_binaries.c.architecture,
2130                                  architecture = relation(Architecture),
2131                                  poolfile_id = self.tbl_binaries.c.file,
2132                                  poolfile = relation(PoolFile),
2133                                  binarytype = self.tbl_binaries.c.type,
2134                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2135                                  fingerprint = relation(Fingerprint),
2136                                  install_date = self.tbl_binaries.c.install_date,
2137                                  binassociations = relation(BinAssociation,
2138                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2139
2140         mapper(Component, self.tbl_component,
2141                properties = dict(component_id = self.tbl_component.c.id,
2142                                  component_name = self.tbl_component.c.name))
2143
2144         mapper(DBConfig, self.tbl_config,
2145                properties = dict(config_id = self.tbl_config.c.id))
2146
2147         mapper(DSCFile, self.tbl_dsc_files,
2148                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2149                                  source_id = self.tbl_dsc_files.c.source,
2150                                  source = relation(DBSource),
2151                                  poolfile_id = self.tbl_dsc_files.c.file,
2152                                  poolfile = relation(PoolFile)))
2153
2154         mapper(PoolFile, self.tbl_files,
2155                properties = dict(file_id = self.tbl_files.c.id,
2156                                  filesize = self.tbl_files.c.size,
2157                                  location_id = self.tbl_files.c.location,
2158                                  location = relation(Location)))
2159
2160         mapper(Fingerprint, self.tbl_fingerprint,
2161                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2162                                  uid_id = self.tbl_fingerprint.c.uid,
2163                                  uid = relation(Uid),
2164                                  keyring_id = self.tbl_fingerprint.c.keyring,
2165                                  keyring = relation(Keyring)))
2166
2167         mapper(Keyring, self.tbl_keyrings,
2168                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2169                                  keyring_id = self.tbl_keyrings.c.id))
2170
2171         mapper(Location, self.tbl_location,
2172                properties = dict(location_id = self.tbl_location.c.id,
2173                                  component_id = self.tbl_location.c.component,
2174                                  component = relation(Component),
2175                                  archive_id = self.tbl_location.c.archive,
2176                                  archive = relation(Archive),
2177                                  archive_type = self.tbl_location.c.type))
2178
2179         mapper(Maintainer, self.tbl_maintainer,
2180                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2181
2182         mapper(NewComment, self.tbl_new_comments,
2183                properties = dict(comment_id = self.tbl_new_comments.c.id))
2184
2185         mapper(Override, self.tbl_override,
2186                properties = dict(suite_id = self.tbl_override.c.suite,
2187                                  suite = relation(Suite),
2188                                  component_id = self.tbl_override.c.component,
2189                                  component = relation(Component),
2190                                  priority_id = self.tbl_override.c.priority,
2191                                  priority = relation(Priority),
2192                                  section_id = self.tbl_override.c.section,
2193                                  section = relation(Section),
2194                                  overridetype_id = self.tbl_override.c.type,
2195                                  overridetype = relation(OverrideType)))
2196
2197         mapper(OverrideType, self.tbl_override_type,
2198                properties = dict(overridetype = self.tbl_override_type.c.type,
2199                                  overridetype_id = self.tbl_override_type.c.id))
2200
2201         mapper(Priority, self.tbl_priority,
2202                properties = dict(priority_id = self.tbl_priority.c.id))
2203
2204         mapper(Queue, self.tbl_queue,
2205                properties = dict(queue_id = self.tbl_queue.c.id))
2206
2207         mapper(QueueBuild, self.tbl_queue_build,
2208                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2209                                  queue_id = self.tbl_queue_build.c.queue,
2210                                  queue = relation(Queue, backref='queuebuild')))
2211
2212         mapper(Section, self.tbl_section,
2213                properties = dict(section_id = self.tbl_section.c.id))
2214
2215         mapper(DBSource, self.tbl_source,
2216                properties = dict(source_id = self.tbl_source.c.id,
2217                                  version = self.tbl_source.c.version,
2218                                  maintainer_id = self.tbl_source.c.maintainer,
2219                                  maintainer = relation(Maintainer,
2220                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2221                                  poolfile_id = self.tbl_source.c.file,
2222                                  poolfile = relation(PoolFile),
2223                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2224                                  fingerprint = relation(Fingerprint),
2225                                  changedby_id = self.tbl_source.c.changedby,
2226                                  changedby = relation(Maintainer,
2227                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2228                                  srcfiles = relation(DSCFile,
2229                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2230                                  srcassociations = relation(SrcAssociation,
2231                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2232
2233         mapper(SrcAssociation, self.tbl_src_associations,
2234                properties = dict(sa_id = self.tbl_src_associations.c.id,
2235                                  suite_id = self.tbl_src_associations.c.suite,
2236                                  suite = relation(Suite),
2237                                  source_id = self.tbl_src_associations.c.source,
2238                                  source = relation(DBSource)))
2239
2240         mapper(SrcFormat, self.tbl_src_format,
2241                properties = dict(src_format_id = self.tbl_src_format.c.id,
2242                                  format_name = self.tbl_src_format.c.format_name))
2243
2244         mapper(SrcUploader, self.tbl_src_uploaders,
2245                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2246                                  source_id = self.tbl_src_uploaders.c.source,
2247                                  source = relation(DBSource,
2248                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2249                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2250                                  maintainer = relation(Maintainer,
2251                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2252
2253         mapper(Suite, self.tbl_suite,
2254                properties = dict(suite_id = self.tbl_suite.c.id))
2255
2256         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2257                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2258                                  suite = relation(Suite, backref='suitearchitectures'),
2259                                  arch_id = self.tbl_suite_architectures.c.architecture,
2260                                  architecture = relation(Architecture)))
2261
2262         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2263                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2264                                  suite = relation(Suite, backref='suitesrcformats'),
2265                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
2266                                  src_format = relation(SrcFormat)))
2267
2268         mapper(Uid, self.tbl_uid,
2269                properties = dict(uid_id = self.tbl_uid.c.id,
2270                                  fingerprint = relation(Fingerprint)))
2271
2272     ## Connection functions
2273     def __createconn(self):
2274         from config import Config
2275         cnf = Config()
2276         if cnf["DB::Host"]:
2277             # TCP/IP
2278             connstr = "postgres://%s" % cnf["DB::Host"]
2279             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2280                 connstr += ":%s" % cnf["DB::Port"]
2281             connstr += "/%s" % cnf["DB::Name"]
2282         else:
2283             # Unix Socket
2284             connstr = "postgres:///%s" % cnf["DB::Name"]
2285             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2286                 connstr += "?port=%s" % cnf["DB::Port"]
2287
2288         self.db_pg   = create_engine(connstr, echo=self.debug)
2289         self.db_meta = MetaData()
2290         self.db_meta.bind = self.db_pg
2291         self.db_smaker = sessionmaker(bind=self.db_pg,
2292                                       autoflush=True,
2293                                       autocommit=False)
2294
2295         self.__setuptables()
2296         self.__setupmappers()
2297
2298     def session(self):
2299         return self.db_smaker()
2300
2301 __all__.append('DBConn')
2302
2303