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