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