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
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.
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.
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
27 ################################################################################
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"
34 ################################################################################
40 from sqlalchemy import create_engine, Table, MetaData, select
41 from sqlalchemy.orm import sessionmaker, mapper, relation
43 # Don't remove this, we re-export the exceptions to scripts which import us
44 from sqlalchemy.exc import *
46 # Only import Config until Queue stuff is changed to store its config
48 from config import Config
49 from singleton import Singleton
50 from textutils import fix_maintainer
52 ################################################################################
54 __all__ = ['IntegrityError', 'SQLAlchemyError']
56 ################################################################################
58 class Architecture(object):
59 def __init__(self, *args, **kwargs):
63 return '<Architecture %s>' % self.arch_string
65 __all__.append('Architecture')
67 def get_architecture(architecture, session=None):
69 Returns database id for given C{architecture}.
71 @type architecture: string
72 @param architecture: The name of the architecture
74 @type session: Session
75 @param session: Optional SQLA session object (a temporary one will be
76 generated if not supplied)
79 @return: Architecture object for the given arch (None if not present)
83 session = DBConn().session()
84 q = session.query(Architecture).filter_by(arch_string=architecture)
89 __all__.append('get_architecture')
91 def get_architecture_suites(architecture, session=None):
93 Returns list of Suite objects for given C{architecture} name
96 @param source: Architecture name to search for
98 @type session: Session
99 @param session: Optional SQL session object (a temporary one will be
100 generated if not supplied)
103 @return: list of Suite objects for the given name (may be empty)
107 session = DBConn().session()
109 q = session.query(Suite)
110 q = q.join(SuiteArchitecture)
111 q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
114 __all__.append('get_architecture_suites')
116 ################################################################################
118 class Archive(object):
119 def __init__(self, *args, **kwargs):
123 return '<Archive %s>' % self.name
125 __all__.append('Archive')
127 def get_archive(archive, session=None):
129 returns database id for given c{archive}.
131 @type archive: string
132 @param archive: the name of the arhive
134 @type session: Session
135 @param session: Optional SQLA session object (a temporary one will be
136 generated if not supplied)
139 @return: Archive object for the given name (None if not present)
142 archive = archive.lower()
144 session = DBConn().session()
145 q = session.query(Archive).filter_by(archive_name=archive)
150 __all__.append('get_archive')
152 ################################################################################
154 class BinAssociation(object):
155 def __init__(self, *args, **kwargs):
159 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
161 __all__.append('BinAssociation')
163 ################################################################################
165 class DBBinary(object):
166 def __init__(self, *args, **kwargs):
170 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
172 __all__.append('DBBinary')
174 def get_binary_from_id(id, session=None):
176 Returns DBBinary object for given C{id}
179 @param id: Id of the required binary
181 @type session: Session
182 @param session: Optional SQLA session object (a temporary one will be
183 generated if not supplied)
186 @return: DBBinary object for the given binary (None if not present)
189 session = DBConn().session()
190 q = session.query(DBBinary).filter_by(binary_id=id)
195 __all__.append('get_binary_from_id')
197 def get_binaries_from_name(package, session=None):
199 Returns list of DBBinary objects for given C{package} name
202 @param package: DBBinary package name to search for
204 @type session: Session
205 @param session: Optional SQL session object (a temporary one will be
206 generated if not supplied)
209 @return: list of DBBinary objects for the given name (may be empty)
212 session = DBConn().session()
213 return session.query(DBBinary).filter_by(package=package).all()
215 __all__.append('get_binaries_from_name')
217 def get_binary_from_name_suite(package, suitename, session=None):
218 ### For dak examine-package
219 ### XXX: Doesn't use object API yet
221 session = DBConn().session()
223 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
224 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
225 WHERE b.package=:package
227 AND fi.location = l.id
228 AND l.component = c.id
231 AND su.suite_name=:suitename
232 ORDER BY b.version DESC"""
234 return session.execute(sql, {'package': package, 'suitename': suitename})
236 __all__.append('get_binary_from_name_suite')
238 def get_binary_components(package, suitename, arch, session=None):
239 # Check for packages that have moved from one component to another
240 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
241 WHERE b.package=:package AND s.suite_name=:suitename
242 AND (a.arch_string = :arch OR a.arch_string = 'all')
243 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
244 AND f.location = l.id
245 AND l.component = c.id
248 vals = {'package': package, 'suitename': suitename, 'arch': arch}
251 session = DBConn().session()
252 return session.execute(query, vals)
254 __all__.append('get_binary_components')
255 ################################################################################
257 class Component(object):
258 def __init__(self, *args, **kwargs):
262 return '<Component %s>' % self.component_name
265 __all__.append('Component')
267 def get_component(component, session=None):
269 Returns database id for given C{component}.
271 @type component: string
272 @param component: The name of the override type
275 @return: the database id for the given component
278 component = component.lower()
280 session = DBConn().session()
281 q = session.query(Component).filter_by(component_name=component)
286 __all__.append('get_component')
288 ################################################################################
290 class DBConfig(object):
291 def __init__(self, *args, **kwargs):
295 return '<DBConfig %s>' % self.name
297 __all__.append('DBConfig')
299 ################################################################################
301 class ContentFilename(object):
302 def __init__(self, *args, **kwargs):
306 return '<ContentFilename %s>' % self.filename
308 __all__.append('ContentFilename')
310 def get_or_set_contents_file_id(filename, session=None):
312 Returns database id for given filename.
314 If no matching file is found, a row is inserted.
316 @type filename: string
317 @param filename: The filename
318 @type session: SQLAlchemy
319 @param session: Optional SQL session object (a temporary one will be
320 generated if not supplied). If not passed, a commit will be performed at
321 the end of the function, otherwise the caller is responsible for commiting.
324 @return: the database id for the given component
328 session = DBConn().session()
332 q = session.query(ContentFilename).filter_by(filename=filename)
334 cf = ContentFilename()
335 cf.filename = filename
339 return cf.cafilename_id
341 return q.one().cafilename_id
344 traceback.print_exc()
347 __all__.append('get_or_set_contents_file_id')
349 def get_contents(suite, overridetype, section=None, session=None):
351 Returns contents for a suite / overridetype combination, limiting
352 to a section if not None.
355 @param suite: Suite object
357 @type overridetype: OverrideType
358 @param overridetype: OverrideType object
360 @type section: Section
361 @param section: Optional section object to limit results to
363 @type session: SQLAlchemy
364 @param session: Optional SQL session object (a temporary one will be
365 generated if not supplied)
368 @return: ResultsProxy object set up to return tuples of (filename, section,
373 session = DBConn().session()
375 # find me all of the contents for a given suite
376 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
380 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
381 JOIN content_file_names n ON (c.filename=n.id)
382 JOIN binaries b ON (b.id=c.binary_pkg)
383 JOIN override o ON (o.package=b.package)
384 JOIN section s ON (s.id=o.section)
385 WHERE o.suite = :suiteid AND o.type = :overridetypeid
386 AND b.type=:overridetypename"""
388 vals = {'suiteid': suite.suite_id,
389 'overridetypeid': overridetype.overridetype_id,
390 'overridetypename': overridetype.overridetype}
392 if section is not None:
393 contents_q += " AND s.id = :sectionid"
394 vals['sectionid'] = section.section_id
396 contents_q += " ORDER BY fn"
398 return session.execute(contents_q, vals)
400 __all__.append('get_contents')
402 ################################################################################
404 class ContentFilepath(object):
405 def __init__(self, *args, **kwargs):
409 return '<ContentFilepath %s>' % self.filepath
411 __all__.append('ContentFilepath')
413 def get_or_set_contents_path_id(filepath, session):
415 Returns database id for given path.
417 If no matching file is found, a row is inserted.
419 @type filename: string
420 @param filename: The filepath
421 @type session: SQLAlchemy
422 @param session: Optional SQL session object (a temporary one will be
423 generated if not supplied). If not passed, a commit will be performed at
424 the end of the function, otherwise the caller is responsible for commiting.
427 @return: the database id for the given path
431 session = DBConn().session()
435 q = session.query(ContentFilepath).filter_by(filepath=filepath)
437 cf = ContentFilepath()
438 cf.filepath = filepath
442 return cf.cafilepath_id
444 return q.one().cafilepath_id
447 traceback.print_exc()
450 __all__.append('get_or_set_contents_path_id')
452 ################################################################################
454 class ContentAssociation(object):
455 def __init__(self, *args, **kwargs):
459 return '<ContentAssociation %s>' % self.ca_id
461 __all__.append('ContentAssociation')
463 def insert_content_paths(binary_id, fullpaths, session=None):
465 Make sure given path is associated with given binary id
468 @param binary_id: the id of the binary
469 @type fullpaths: list
470 @param fullpaths: the list of paths of the file being associated with the binary
471 @type session: SQLAlchemy session
472 @param session: Optional SQLAlchemy session. If this is passed, the caller
473 is responsible for ensuring a transaction has begun and committing the
474 results or rolling back based on the result code. If not passed, a commit
475 will be performed at the end of the function, otherwise the caller is
476 responsible for commiting.
478 @return: True upon success
484 session = DBConn().session()
488 for fullpath in fullpaths:
489 (path, file) = os.path.split(fullpath)
491 # Get the necessary IDs ...
492 ca = ContentAssociation()
493 ca.binary_id = binary_id
494 ca.filename_id = get_or_set_contents_file_id(file)
495 ca.filepath_id = get_or_set_contents_path_id(path)
498 # Only commit if we set up the session ourself
504 traceback.print_exc()
506 # Only rollback if we set up the session ourself
512 __all__.append('insert_content_paths')
514 ################################################################################
516 class DSCFile(object):
517 def __init__(self, *args, **kwargs):
521 return '<DSCFile %s>' % self.dscfile_id
523 __all__.append('DSCFile')
525 ################################################################################
527 class PoolFile(object):
528 def __init__(self, *args, **kwargs):
532 return '<PoolFile %s>' % self.filename
534 __all__.append('PoolFile')
536 def get_poolfile_by_name(filename, location_id=None, session=None):
538 Returns an array of PoolFile objects for the given filename and
539 (optionally) location_id
541 @type filename: string
542 @param filename: the filename of the file to check against the DB
544 @type location_id: int
545 @param location_id: the id of the location to look in (optional)
548 @return: array of PoolFile objects
551 if session is not None:
552 session = DBConn().session()
554 q = session.query(PoolFile).filter_by(filename=filename)
556 if location_id is not None:
557 q = q.join(Location).filter_by(location_id=location_id)
561 __all__.append('get_poolfile_by_name')
563 def get_poolfile_like_name(filename, session=None):
565 Returns an array of PoolFile objects which are like the given name
567 @type filename: string
568 @param filename: the filename of the file to check against the DB
571 @return: array of PoolFile objects
574 if session is not None:
575 session = DBConn().session()
577 # TODO: There must be a way of properly using bind parameters with %FOO%
578 q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
582 __all__.append('get_poolfile_like_name')
584 ################################################################################
586 class Fingerprint(object):
587 def __init__(self, *args, **kwargs):
591 return '<Fingerprint %s>' % self.fingerprint
593 __all__.append('Fingerprint')
595 ################################################################################
597 class Keyring(object):
598 def __init__(self, *args, **kwargs):
602 return '<Keyring %s>' % self.keyring_name
604 __all__.append('Keyring')
606 ################################################################################
608 class Location(object):
609 def __init__(self, *args, **kwargs):
613 return '<Location %s (%s)>' % (self.path, self.location_id)
615 __all__.append('Location')
617 def get_location(location, component=None, archive=None, session=None):
619 Returns Location object for the given combination of location, component
622 @type location: string
623 @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
625 @type component: string
626 @param component: the component name (if None, no restriction applied)
628 @type archive: string
629 @param archive_id: the archive name (if None, no restriction applied)
631 @rtype: Location / None
632 @return: Either a Location object or None if one can't be found
636 session = DBConn().session()
638 q = session.query(Location).filter_by(path=location)
640 if archive is not None:
641 q = q.join(Archive).filter_by(archive_name=archive)
643 if component is not None:
644 q = q.join(Component).filter_by(component_name=component)
651 __all__.append('get_location')
653 ################################################################################
655 class Maintainer(object):
656 def __init__(self, *args, **kwargs):
660 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
662 def get_split_maintainer(self):
663 if not hasattr(self, 'name') or self.name is None:
664 return ('', '', '', '')
666 return fix_maintainer(self.name.strip())
668 __all__.append('Maintainer')
670 ################################################################################
672 class NewComment(object):
673 def __init__(self, *args, **kwargs):
677 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
679 __all__.append('NewComment')
681 def has_new_comment(package, version, session=None):
683 Returns true if the given combination of C{package}, C{version} has a comment.
685 @type package: string
686 @param package: name of the package
688 @type version: string
689 @param version: package version
691 @type session: Session
692 @param session: Optional SQLA session object (a temporary one will be
693 generated if not supplied)
700 session = DBConn().session()
702 q = session.query(NewComment)
703 q = q.filter_by(package=package)
704 q = q.filter_by(version=version)
707 __all__.append('has_new_comment')
709 def get_new_comments(package=None, version=None, comment_id=None, session=None):
711 Returns (possibly empty) list of NewComment objects for the given
714 @type package: string (optional)
715 @param package: name of the package
717 @type version: string (optional)
718 @param version: package version
720 @type comment_id: int (optional)
721 @param comment_id: An id of a comment
723 @type session: Session
724 @param session: Optional SQLA session object (a temporary one will be
725 generated if not supplied)
728 @return: A (possibly empty) list of NewComment objects will be returned
733 session = DBConn().session()
735 q = session.query(NewComment)
736 if package is not None: q = q.filter_by(package=package)
737 if version is not None: q = q.filter_by(version=version)
738 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
742 __all__.append('get_new_comments')
744 ################################################################################
746 class Override(object):
747 def __init__(self, *args, **kwargs):
751 return '<Override %s (%s)>' % (self.package, self.suite_id)
753 __all__.append('Override')
755 def get_override(package, suite=None, component=None, overridetype=None, session=None):
757 Returns Override object for the given parameters
759 @type package: string
760 @param package: The name of the package
762 @type suite: string, list or None
763 @param suite: The name of the suite (or suites if a list) to limit to. If
764 None, don't limit. Defaults to None.
766 @type component: string, list or None
767 @param component: The name of the component (or components if a list) to
768 limit to. If None, don't limit. Defaults to None.
770 @type overridetype: string, list or None
771 @param overridetype: The name of the overridetype (or overridetypes if a list) to
772 limit to. If None, don't limit. Defaults to None.
774 @type session: Session
775 @param session: Optional SQLA session object (a temporary one will be
776 generated if not supplied)
779 @return: A (possibly empty) list of Override objects will be returned
783 session = DBConn().session()
785 q = session.query(Override)
786 q = q.filter_by(package=package)
788 if suite is not None:
789 if not isinstance(suite, list): suite = [suite]
790 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
792 if component is not None:
793 if not isinstance(component, list): component = [component]
794 q = q.join(Component).filter(Component.component_name.in_(component))
796 if overridetype is not None:
797 if not isinstance(overridetype, list): overridetype = [overridetype]
798 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
802 __all__.append('get_override')
805 ################################################################################
807 class OverrideType(object):
808 def __init__(self, *args, **kwargs):
812 return '<OverrideType %s>' % self.overridetype
814 __all__.append('OverrideType')
816 def get_override_type(override_type, session=None):
818 Returns OverrideType object for given C{override type}.
820 @type override_type: string
821 @param override_type: The name of the override type
823 @type session: Session
824 @param session: Optional SQLA session object (a temporary one will be
825 generated if not supplied)
828 @return: the database id for the given override type
832 session = DBConn().session()
833 q = session.query(OverrideType).filter_by(overridetype=override_type)
838 __all__.append('get_override_type')
840 ################################################################################
842 class PendingContentAssociation(object):
843 def __init__(self, *args, **kwargs):
847 return '<PendingContentAssociation %s>' % self.pca_id
849 __all__.append('PendingContentAssociation')
851 def insert_pending_content_paths(package, fullpaths, session=None):
853 Make sure given paths are temporarily associated with given
857 @param package: the package to associate with should have been read in from the binary control file
858 @type fullpaths: list
859 @param fullpaths: the list of paths of the file being associated with the binary
860 @type session: SQLAlchemy session
861 @param session: Optional SQLAlchemy session. If this is passed, the caller
862 is responsible for ensuring a transaction has begun and committing the
863 results or rolling back based on the result code. If not passed, a commit
864 will be performed at the end of the function
866 @return: True upon success, False if there is a problem
872 session = DBConn().session()
876 arch = get_architecture(package['Architecture'], session)
877 arch_id = arch.arch_id
879 # Remove any already existing recorded files for this package
880 q = session.query(PendingContentAssociation)
881 q = q.filter_by(package=package['Package'])
882 q = q.filter_by(version=package['Version'])
883 q = q.filter_by(architecture=arch_id)
887 for fullpath in fullpaths:
888 (path, file) = os.path.split(fullpath)
890 if path.startswith( "./" ):
893 pca = PendingContentAssociation()
894 pca.package = package['Package']
895 pca.version = package['Version']
896 pca.filename_id = get_or_set_contents_file_id(file, session)
897 pca.filepath_id = get_or_set_contents_path_id(path, session)
898 pca.architecture = arch_id
901 # Only commit if we set up the session ourself
907 traceback.print_exc()
909 # Only rollback if we set up the session ourself
915 __all__.append('insert_pending_content_paths')
917 ################################################################################
919 class Priority(object):
920 def __init__(self, *args, **kwargs):
924 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
926 __all__.append('Priority')
928 def get_priority(priority, session=None):
930 Returns Priority object for given C{priority name}.
932 @type priority: string
933 @param priority: The name of the priority
935 @type session: Session
936 @param session: Optional SQLA session object (a temporary one will be
937 generated if not supplied)
940 @return: Priority object for the given priority
944 session = DBConn().session()
945 q = session.query(Priority).filter_by(priority=priority)
950 __all__.append('get_priority')
952 ################################################################################
955 def __init__(self, *args, **kwargs):
959 return '<Queue %s>' % self.queue_name
961 def autobuild_upload(self, changes, srcpath, session=None):
963 Update queue_build database table used for incoming autobuild support.
965 @type changes: Changes
966 @param changes: changes object for the upload to process
968 @type srcpath: string
969 @param srcpath: path for the queue file entries/link destinations
971 @type session: SQLAlchemy session
972 @param session: Optional SQLAlchemy session. If this is passed, the
973 caller is responsible for ensuring a transaction has begun and
974 committing the results or rolling back based on the result code. If
975 not passed, a commit will be performed at the end of the function,
976 otherwise the caller is responsible for commiting.
978 @rtype: NoneType or string
979 @return: None if the operation failed, a string describing the error if not
984 session = DBConn().session()
987 # TODO: Remove by moving queue config into the database
990 for suitename in changes.changes["distribution"].keys():
991 # TODO: Move into database as:
992 # buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
993 # buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
994 # This also gets rid of the SecurityQueueBuild hack below
995 if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
999 s = get_suite(suitename, session)
1001 return "INTERNAL ERROR: Could not find suite %s" % suitename
1003 # TODO: Get from database as above
1004 dest_dir = conf["Dir::QueueBuild"]
1006 # TODO: Move into database as above
1007 if conf.FindB("Dinstall::SecurityQueueBuild"):
1008 dest_dir = os.path.join(dest_dir, suitename)
1010 for file_entry in changes.files.keys():
1011 src = os.path.join(srcpath, file_entry)
1012 dest = os.path.join(dest_dir, file_entry)
1014 # TODO: Move into database as above
1015 if Cnf.FindB("Dinstall::SecurityQueueBuild"):
1016 # Copy it since the original won't be readable by www-data
1017 utils.copy(src, dest)
1019 # Create a symlink to it
1020 os.symlink(src, dest)
1023 qb.suite_id = s.suite_id
1024 qb.queue_id = self.queue_id
1030 # If the .orig.tar.gz is in the pool, create a symlink to
1031 # it (if one doesn't already exist)
1032 if changes.orig_tar_id:
1033 # Determine the .orig.tar.gz file name
1034 for dsc_file in changes.dsc_files.keys():
1035 if dsc_file.endswith(".orig.tar.gz"):
1038 dest = os.path.join(dest_dir, filename)
1040 # If it doesn't exist, create a symlink
1041 if not os.path.exists(dest):
1042 q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1043 {'id': changes.orig_tar_id})
1046 return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1048 src = os.path.join(res[0], res[1])
1049 os.symlink(src, dest)
1051 # Add it to the list of packages for later processing by apt-ftparchive
1053 qb.suite_id = s.suite_id
1054 qb.queue_id = self.queue_id
1059 # If it does, update things to ensure it's not removed prematurely
1061 qb = get_queue_build(dest, suite_id, session)
1072 __all__.append('Queue')
1074 def get_queue(queuename, session=None):
1076 Returns Queue object for given C{queue name}.
1078 @type queuename: string
1079 @param queuename: The name of the queue
1081 @type session: Session
1082 @param session: Optional SQLA session object (a temporary one will be
1083 generated if not supplied)
1086 @return: Queue object for the given queue
1090 session = DBConn().session()
1091 q = session.query(Queue).filter_by(queue_name=queuename)
1096 __all__.append('get_queue')
1098 ################################################################################
1100 class QueueBuild(object):
1101 def __init__(self, *args, **kwargs):
1105 return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1107 __all__.append('QueueBuild')
1109 def get_queue_build(filename, suite_id, session=None):
1111 Returns QueueBuild object for given C{filename} and C{suite id}.
1113 @type filename: string
1114 @param filename: The name of the file
1117 @param suiteid: Suite ID
1119 @type session: Session
1120 @param session: Optional SQLA session object (a temporary one will be
1121 generated if not supplied)
1124 @return: Queue object for the given queue
1128 session = DBConn().session()
1129 q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite_id)
1134 __all__.append('get_queue_build')
1136 ################################################################################
1138 class Section(object):
1139 def __init__(self, *args, **kwargs):
1143 return '<Section %s>' % self.section
1145 __all__.append('Section')
1147 def get_section(section, session=None):
1149 Returns Section object for given C{section name}.
1151 @type section: string
1152 @param section: The name of the section
1154 @type session: Session
1155 @param session: Optional SQLA session object (a temporary one will be
1156 generated if not supplied)
1159 @return: Section object for the given section name
1163 session = DBConn().session()
1164 q = session.query(Section).filter_by(section=section)
1169 __all__.append('get_section')
1171 ################################################################################
1173 class DBSource(object):
1174 def __init__(self, *args, **kwargs):
1178 return '<DBSource %s (%s)>' % (self.source, self.version)
1180 __all__.append('DBSource')
1182 def source_exists(source, source_version, suites = ["any"], session=None):
1184 Ensure that source exists somewhere in the archive for the binary
1185 upload being processed.
1186 1. exact match => 1.0-3
1187 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1189 @type package: string
1190 @param package: package source name
1192 @type source_version: string
1193 @param source_version: expected source version
1196 @param suites: list of suites to check in, default I{any}
1198 @type session: Session
1199 @param session: Optional SQLA session object (a temporary one will be
1200 generated if not supplied)
1203 @return: returns 1 if a source with expected version is found, otherwise 0
1208 session = DBConn().session()
1212 for suite in suites:
1213 q = session.query(DBSource).filter_by(source=source)
1215 # source must exist in suite X, or in some other suite that's
1216 # mapped to X, recursively... silent-maps are counted too,
1217 # unreleased-maps aren't.
1218 maps = cnf.ValueList("SuiteMappings")[:]
1220 maps = [ m.split() for m in maps ]
1221 maps = [ (x[1], x[2]) for x in maps
1222 if x[0] == "map" or x[0] == "silent-map" ]
1225 if x[1] in s and x[0] not in s:
1228 q = q.join(SrcAssociation).join(Suite)
1229 q = q.filter(Suite.suite_name.in_(s))
1231 # Reduce the query results to a list of version numbers
1232 ql = [ j.version for j in q.all() ]
1235 if source_version in ql:
1239 from daklib.regexes import re_bin_only_nmu
1240 orig_source_version = re_bin_only_nmu.sub('', source_version)
1241 if orig_source_version in ql:
1244 # No source found so return not ok
1250 __all__.append('source_exists')
1252 def get_sources_from_name(source, dm_upload_allowed=None, session=None):
1254 Returns list of DBSource objects for given C{source} name
1257 @param source: DBSource package name to search for
1259 @type dm_upload_allowed: bool
1260 @param dm_upload_allowed: If None, no effect. If True or False, only
1261 return packages with that dm_upload_allowed setting
1263 @type session: Session
1264 @param session: Optional SQL session object (a temporary one will be
1265 generated if not supplied)
1268 @return: list of DBSource objects for the given name (may be empty)
1271 session = DBConn().session()
1273 q = session.query(DBSource).filter_by(source=source)
1274 if dm_upload_allowed is not None:
1275 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1279 __all__.append('get_sources_from_name')
1281 def get_source_in_suite(source, suite, session=None):
1283 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1285 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1286 - B{suite} - a suite name, eg. I{unstable}
1288 @type source: string
1289 @param source: source package name
1292 @param suite: the suite name
1295 @return: the version for I{source} in I{suite}
1299 session = DBConn().session()
1300 q = session.query(SrcAssociation)
1301 q = q.join('source').filter_by(source=source)
1302 q = q.join('suite').filter_by(suite_name=suite)
1305 # ???: Maybe we should just return the SrcAssociation object instead
1306 return q.one().source
1308 __all__.append('get_source_in_suite')
1310 ################################################################################
1312 class SrcAssociation(object):
1313 def __init__(self, *args, **kwargs):
1317 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1319 __all__.append('SrcAssociation')
1321 ################################################################################
1323 class SrcUploader(object):
1324 def __init__(self, *args, **kwargs):
1328 return '<SrcUploader %s>' % self.uploader_id
1330 __all__.append('SrcUploader')
1332 ################################################################################
1334 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1335 ('SuiteID', 'suite_id'),
1336 ('Version', 'version'),
1337 ('Origin', 'origin'),
1339 ('Description', 'description'),
1340 ('Untouchable', 'untouchable'),
1341 ('Announce', 'announce'),
1342 ('Codename', 'codename'),
1343 ('OverrideCodename', 'overridecodename'),
1344 ('ValidTime', 'validtime'),
1345 ('Priority', 'priority'),
1346 ('NotAutomatic', 'notautomatic'),
1347 ('CopyChanges', 'copychanges'),
1348 ('CopyDotDak', 'copydotdak'),
1349 ('CommentsDir', 'commentsdir'),
1350 ('OverrideSuite', 'overridesuite'),
1351 ('ChangelogBase', 'changelogbase')]
1354 class Suite(object):
1355 def __init__(self, *args, **kwargs):
1359 return '<Suite %s>' % self.suite_name
1363 for disp, field in SUITE_FIELDS:
1364 val = getattr(self, field, None)
1366 ret.append("%s: %s" % (disp, val))
1368 return "\n".join(ret)
1370 __all__.append('Suite')
1372 def get_suite_architecture(suite, architecture, session=None):
1374 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1378 @param suite: Suite name to search for
1380 @type architecture: str
1381 @param architecture: Architecture name to search for
1383 @type session: Session
1384 @param session: Optional SQL session object (a temporary one will be
1385 generated if not supplied)
1387 @rtype: SuiteArchitecture
1388 @return: the SuiteArchitecture object or None
1392 session = DBConn().session()
1394 q = session.query(SuiteArchitecture)
1395 q = q.join(Architecture).filter_by(arch_string=architecture)
1396 q = q.join(Suite).filter_by(suite_name=suite)
1401 __all__.append('get_suite_architecture')
1403 def get_suite(suite, session=None):
1405 Returns Suite object for given C{suite name}.
1408 @param suite: The name of the suite
1410 @type session: Session
1411 @param session: Optional SQLA session object (a temporary one will be
1412 generated if not supplied)
1415 @return: Suite object for the requested suite name (None if not presenT)
1419 session = DBConn().session()
1420 q = session.query(Suite).filter_by(suite_name=suite)
1425 __all__.append('get_suite')
1427 ################################################################################
1429 class SuiteArchitecture(object):
1430 def __init__(self, *args, **kwargs):
1434 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1436 __all__.append('SuiteArchitecture')
1438 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1440 Returns list of Architecture objects for given C{suite} name
1443 @param source: Suite name to search for
1445 @type skipsrc: boolean
1446 @param skipsrc: Whether to skip returning the 'source' architecture entry
1449 @type skipall: boolean
1450 @param skipall: Whether to skip returning the 'all' architecture entry
1453 @type session: Session
1454 @param session: Optional SQL session object (a temporary one will be
1455 generated if not supplied)
1458 @return: list of Architecture objects for the given name (may be empty)
1462 session = DBConn().session()
1464 q = session.query(Architecture)
1465 q = q.join(SuiteArchitecture)
1466 q = q.join(Suite).filter_by(suite_name=suite)
1468 q = q.filter(Architecture.arch_string != 'source')
1470 q = q.filter(Architecture.arch_string != 'all')
1471 q = q.order_by('arch_string')
1474 __all__.append('get_suite_architectures')
1476 ################################################################################
1479 def __init__(self, *args, **kwargs):
1483 return '<Uid %s (%s)>' % (self.uid, self.name)
1485 __all__.append('Uid')
1487 def add_database_user(uidname, session=None):
1489 Adds a database user
1491 @type uidname: string
1492 @param uidname: The uid of the user to add
1494 @type session: SQLAlchemy
1495 @param session: Optional SQL session object (a temporary one will be
1496 generated if not supplied). If not passed, a commit will be performed at
1497 the end of the function, otherwise the caller is responsible for commiting.
1500 @return: the uid object for the given uidname
1502 privatetrans = False
1504 session = DBConn().session()
1508 session.execute("CREATE USER :uid", {'uid': uidname})
1512 traceback.print_exc()
1515 __all__.append('add_database_user')
1517 def get_or_set_uid(uidname, session=None):
1519 Returns uid object for given uidname.
1521 If no matching uidname is found, a row is inserted.
1523 @type uidname: string
1524 @param uidname: The uid to add
1526 @type session: SQLAlchemy
1527 @param session: Optional SQL session object (a temporary one will be
1528 generated if not supplied). If not passed, a commit will be performed at
1529 the end of the function, otherwise the caller is responsible for commiting.
1532 @return: the uid object for the given uidname
1534 privatetrans = False
1536 session = DBConn().session()
1540 q = session.query(Uid).filter_by(uid=uidname)
1552 traceback.print_exc()
1555 __all__.append('get_or_set_uid')
1558 def get_uid_from_fingerprint(fpr, session=None):
1560 session = DBConn().session()
1562 q = session.query(Uid)
1563 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
1570 __all__.append('get_uid_from_fingerprint')
1572 ################################################################################
1574 class DBConn(Singleton):
1576 database module init.
1578 def __init__(self, *args, **kwargs):
1579 super(DBConn, self).__init__(*args, **kwargs)
1581 def _startup(self, *args, **kwargs):
1583 if kwargs.has_key('debug'):
1587 def __setuptables(self):
1588 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
1589 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
1590 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
1591 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
1592 self.tbl_component = Table('component', self.db_meta, autoload=True)
1593 self.tbl_config = Table('config', self.db_meta, autoload=True)
1594 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
1595 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
1596 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
1597 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
1598 self.tbl_files = Table('files', self.db_meta, autoload=True)
1599 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
1600 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
1601 self.tbl_location = Table('location', self.db_meta, autoload=True)
1602 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
1603 self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
1604 self.tbl_override = Table('override', self.db_meta, autoload=True)
1605 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
1606 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
1607 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
1608 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
1609 self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
1610 self.tbl_section = Table('section', self.db_meta, autoload=True)
1611 self.tbl_source = Table('source', self.db_meta, autoload=True)
1612 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
1613 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
1614 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
1615 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
1616 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
1618 def __setupmappers(self):
1619 mapper(Architecture, self.tbl_architecture,
1620 properties = dict(arch_id = self.tbl_architecture.c.id))
1622 mapper(Archive, self.tbl_archive,
1623 properties = dict(archive_id = self.tbl_archive.c.id,
1624 archive_name = self.tbl_archive.c.name))
1626 mapper(BinAssociation, self.tbl_bin_associations,
1627 properties = dict(ba_id = self.tbl_bin_associations.c.id,
1628 suite_id = self.tbl_bin_associations.c.suite,
1629 suite = relation(Suite),
1630 binary_id = self.tbl_bin_associations.c.bin,
1631 binary = relation(DBBinary)))
1633 mapper(DBBinary, self.tbl_binaries,
1634 properties = dict(binary_id = self.tbl_binaries.c.id,
1635 package = self.tbl_binaries.c.package,
1636 version = self.tbl_binaries.c.version,
1637 maintainer_id = self.tbl_binaries.c.maintainer,
1638 maintainer = relation(Maintainer),
1639 source_id = self.tbl_binaries.c.source,
1640 source = relation(DBSource),
1641 arch_id = self.tbl_binaries.c.architecture,
1642 architecture = relation(Architecture),
1643 poolfile_id = self.tbl_binaries.c.file,
1644 poolfile = relation(PoolFile),
1645 binarytype = self.tbl_binaries.c.type,
1646 fingerprint_id = self.tbl_binaries.c.sig_fpr,
1647 fingerprint = relation(Fingerprint),
1648 install_date = self.tbl_binaries.c.install_date,
1649 binassociations = relation(BinAssociation,
1650 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
1652 mapper(Component, self.tbl_component,
1653 properties = dict(component_id = self.tbl_component.c.id,
1654 component_name = self.tbl_component.c.name))
1656 mapper(DBConfig, self.tbl_config,
1657 properties = dict(config_id = self.tbl_config.c.id))
1659 mapper(ContentAssociation, self.tbl_content_associations,
1660 properties = dict(ca_id = self.tbl_content_associations.c.id,
1661 filename_id = self.tbl_content_associations.c.filename,
1662 filename = relation(ContentFilename),
1663 filepath_id = self.tbl_content_associations.c.filepath,
1664 filepath = relation(ContentFilepath),
1665 binary_id = self.tbl_content_associations.c.binary_pkg,
1666 binary = relation(DBBinary)))
1669 mapper(ContentFilename, self.tbl_content_file_names,
1670 properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
1671 filename = self.tbl_content_file_names.c.file))
1673 mapper(ContentFilepath, self.tbl_content_file_paths,
1674 properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
1675 filepath = self.tbl_content_file_paths.c.path))
1677 mapper(DSCFile, self.tbl_dsc_files,
1678 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
1679 source_id = self.tbl_dsc_files.c.source,
1680 source = relation(DBSource),
1681 poolfile_id = self.tbl_dsc_files.c.file,
1682 poolfile = relation(PoolFile)))
1684 mapper(PoolFile, self.tbl_files,
1685 properties = dict(file_id = self.tbl_files.c.id,
1686 filesize = self.tbl_files.c.size,
1687 location_id = self.tbl_files.c.location,
1688 location = relation(Location)))
1690 mapper(Fingerprint, self.tbl_fingerprint,
1691 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
1692 uid_id = self.tbl_fingerprint.c.uid,
1693 uid = relation(Uid),
1694 keyring_id = self.tbl_fingerprint.c.keyring,
1695 keyring = relation(Keyring)))
1697 mapper(Keyring, self.tbl_keyrings,
1698 properties = dict(keyring_name = self.tbl_keyrings.c.name,
1699 keyring_id = self.tbl_keyrings.c.id))
1701 mapper(Location, self.tbl_location,
1702 properties = dict(location_id = self.tbl_location.c.id,
1703 component_id = self.tbl_location.c.component,
1704 component = relation(Component),
1705 archive_id = self.tbl_location.c.archive,
1706 archive = relation(Archive),
1707 archive_type = self.tbl_location.c.type))
1709 mapper(Maintainer, self.tbl_maintainer,
1710 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
1712 mapper(NewComment, self.tbl_new_comments,
1713 properties = dict(comment_id = self.tbl_new_comments.c.id))
1715 mapper(Override, self.tbl_override,
1716 properties = dict(suite_id = self.tbl_override.c.suite,
1717 suite = relation(Suite),
1718 component_id = self.tbl_override.c.component,
1719 component = relation(Component),
1720 priority_id = self.tbl_override.c.priority,
1721 priority = relation(Priority),
1722 section_id = self.tbl_override.c.section,
1723 section = relation(Section),
1724 overridetype_id = self.tbl_override.c.type,
1725 overridetype = relation(OverrideType)))
1727 mapper(OverrideType, self.tbl_override_type,
1728 properties = dict(overridetype = self.tbl_override_type.c.type,
1729 overridetype_id = self.tbl_override_type.c.id))
1731 mapper(PendingContentAssociation, self.tbl_pending_content_associations,
1732 properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
1733 filepath_id = self.tbl_pending_content_associations.c.filepath,
1734 filepath = relation(ContentFilepath),
1735 filename_id = self.tbl_pending_content_associations.c.filename,
1736 filename = relation(ContentFilename)))
1738 mapper(Priority, self.tbl_priority,
1739 properties = dict(priority_id = self.tbl_priority.c.id))
1741 mapper(Queue, self.tbl_queue,
1742 properties = dict(queue_id = self.tbl_queue.c.id))
1744 mapper(QueueBuild, self.tbl_queue_build,
1745 properties = dict(suite_id = self.tbl_queue_build.c.suite,
1746 queue_id = self.tbl_queue_build.c.queue,
1747 queue = relation(Queue, backref='queuebuild')))
1749 mapper(Section, self.tbl_section,
1750 properties = dict(section_id = self.tbl_section.c.id))
1752 mapper(DBSource, self.tbl_source,
1753 properties = dict(source_id = self.tbl_source.c.id,
1754 version = self.tbl_source.c.version,
1755 maintainer_id = self.tbl_source.c.maintainer,
1756 maintainer = relation(Maintainer,
1757 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
1758 poolfile_id = self.tbl_source.c.file,
1759 poolfile = relation(PoolFile),
1760 fingerprint_id = self.tbl_source.c.sig_fpr,
1761 fingerprint = relation(Fingerprint),
1762 changedby_id = self.tbl_source.c.changedby,
1763 changedby = relation(Maintainer,
1764 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
1765 srcfiles = relation(DSCFile,
1766 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
1767 srcassociations = relation(SrcAssociation,
1768 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
1770 mapper(SrcAssociation, self.tbl_src_associations,
1771 properties = dict(sa_id = self.tbl_src_associations.c.id,
1772 suite_id = self.tbl_src_associations.c.suite,
1773 suite = relation(Suite),
1774 source_id = self.tbl_src_associations.c.source,
1775 source = relation(DBSource)))
1777 mapper(SrcUploader, self.tbl_src_uploaders,
1778 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
1779 source_id = self.tbl_src_uploaders.c.source,
1780 source = relation(DBSource,
1781 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
1782 maintainer_id = self.tbl_src_uploaders.c.maintainer,
1783 maintainer = relation(Maintainer,
1784 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
1786 mapper(Suite, self.tbl_suite,
1787 properties = dict(suite_id = self.tbl_suite.c.id))
1789 mapper(SuiteArchitecture, self.tbl_suite_architectures,
1790 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
1791 suite = relation(Suite, backref='suitearchitectures'),
1792 arch_id = self.tbl_suite_architectures.c.architecture,
1793 architecture = relation(Architecture)))
1795 mapper(Uid, self.tbl_uid,
1796 properties = dict(uid_id = self.tbl_uid.c.id,
1797 fingerprint = relation(Fingerprint)))
1799 ## Connection functions
1800 def __createconn(self):
1801 from config import Config
1805 connstr = "postgres://%s" % cnf["DB::Host"]
1806 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1807 connstr += ":%s" % cnf["DB::Port"]
1808 connstr += "/%s" % cnf["DB::Name"]
1811 connstr = "postgres:///%s" % cnf["DB::Name"]
1812 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1813 connstr += "?port=%s" % cnf["DB::Port"]
1815 self.db_pg = create_engine(connstr, echo=self.debug)
1816 self.db_meta = MetaData()
1817 self.db_meta.bind = self.db_pg
1818 self.db_smaker = sessionmaker(bind=self.db_pg,
1822 self.__setuptables()
1823 self.__setupmappers()
1826 return self.db_smaker()
1828 __all__.append('DBConn')