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