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