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 Override(object):
673 def __init__(self, *args, **kwargs):
677 return '<Override %s (%s)>' % (self.package, self.suite_id)
679 __all__.append('Override')
681 def get_override(package, suite=None, component=None, overridetype=None, session=None):
683 Returns Override object for the given parameters
685 @type package: string
686 @param package: The name of the package
688 @type suite: string, list or None
689 @param suite: The name of the suite (or suites if a list) to limit to. If
690 None, don't limit. Defaults to None.
692 @type component: string, list or None
693 @param component: The name of the component (or components if a list) to
694 limit to. If None, don't limit. Defaults to None.
696 @type overridetype: string, list or None
697 @param overridetype: The name of the overridetype (or overridetypes if a list) to
698 limit to. If None, don't limit. Defaults to None.
700 @type session: Session
701 @param session: Optional SQLA session object (a temporary one will be
702 generated if not supplied)
705 @return: A (possibly empty) list of Override objects will be returned
709 session = DBConn().session()
711 q = session.query(Override)
712 q = q.filter_by(package=package)
714 if suite is not None:
715 if not isinstance(suite, list): suite = [suite]
716 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
718 if component is not None:
719 if not isinstance(component, list): component = [component]
720 q = q.join(Component).filter(Component.component_name.in_(component))
722 if overridetype is not None:
723 if not isinstance(overridetype, list): overridetype = [overridetype]
724 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
728 __all__.append('get_override')
731 ################################################################################
733 class OverrideType(object):
734 def __init__(self, *args, **kwargs):
738 return '<OverrideType %s>' % self.overridetype
740 __all__.append('OverrideType')
742 def get_override_type(override_type, session=None):
744 Returns OverrideType object for given C{override type}.
746 @type override_type: string
747 @param override_type: The name of the override type
749 @type session: Session
750 @param session: Optional SQLA session object (a temporary one will be
751 generated if not supplied)
754 @return: the database id for the given override type
758 session = DBConn().session()
759 q = session.query(OverrideType).filter_by(overridetype=override_type)
764 __all__.append('get_override_type')
766 ################################################################################
768 class PendingContentAssociation(object):
769 def __init__(self, *args, **kwargs):
773 return '<PendingContentAssociation %s>' % self.pca_id
775 __all__.append('PendingContentAssociation')
777 def insert_pending_content_paths(package, fullpaths, session=None):
779 Make sure given paths are temporarily associated with given
783 @param package: the package to associate with should have been read in from the binary control file
784 @type fullpaths: list
785 @param fullpaths: the list of paths of the file being associated with the binary
786 @type session: SQLAlchemy session
787 @param session: Optional SQLAlchemy session. If this is passed, the caller
788 is responsible for ensuring a transaction has begun and committing the
789 results or rolling back based on the result code. If not passed, a commit
790 will be performed at the end of the function
792 @return: True upon success, False if there is a problem
798 session = DBConn().session()
802 arch = get_architecture(package['Architecture'], session)
803 arch_id = arch.arch_id
805 # Remove any already existing recorded files for this package
806 q = session.query(PendingContentAssociation)
807 q = q.filter_by(package=package['Package'])
808 q = q.filter_by(version=package['Version'])
809 q = q.filter_by(architecture=arch_id)
813 for fullpath in fullpaths:
814 (path, file) = os.path.split(fullpath)
816 if path.startswith( "./" ):
819 pca = PendingContentAssociation()
820 pca.package = package['Package']
821 pca.version = package['Version']
822 pca.filename_id = get_or_set_contents_file_id(file, session)
823 pca.filepath_id = get_or_set_contents_path_id(path, session)
824 pca.architecture = arch_id
827 # Only commit if we set up the session ourself
833 traceback.print_exc()
835 # Only rollback if we set up the session ourself
841 __all__.append('insert_pending_content_paths')
843 ################################################################################
845 class Priority(object):
846 def __init__(self, *args, **kwargs):
850 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
852 __all__.append('Priority')
854 def get_priority(priority, session=None):
856 Returns Priority object for given C{priority name}.
858 @type priority: string
859 @param priority: The name of the priority
861 @type session: Session
862 @param session: Optional SQLA session object (a temporary one will be
863 generated if not supplied)
866 @return: Priority object for the given priority
870 session = DBConn().session()
871 q = session.query(Priority).filter_by(priority=priority)
876 __all__.append('get_priority')
878 ################################################################################
881 def __init__(self, *args, **kwargs):
885 return '<Queue %s>' % self.queue_name
887 def autobuild_upload(self, changes, srcpath, session=None):
889 Update queue_build database table used for incoming autobuild support.
891 @type changes: Changes
892 @param changes: changes object for the upload to process
894 @type srcpath: string
895 @param srcpath: path for the queue file entries/link destinations
897 @type session: SQLAlchemy session
898 @param session: Optional SQLAlchemy session. If this is passed, the
899 caller is responsible for ensuring a transaction has begun and
900 committing the results or rolling back based on the result code. If
901 not passed, a commit will be performed at the end of the function,
902 otherwise the caller is responsible for commiting.
904 @rtype: NoneType or string
905 @return: None if the operation failed, a string describing the error if not
910 session = DBConn().session()
913 # TODO: Remove by moving queue config into the database
916 for suitename in changes.changes["distribution"].keys():
917 # TODO: Move into database as:
918 # buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
919 # buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
920 # This also gets rid of the SecurityQueueBuild hack below
921 if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
925 s = get_suite(suitename, session)
927 return "INTERNAL ERROR: Could not find suite %s" % suitename
929 # TODO: Get from database as above
930 dest_dir = conf["Dir::QueueBuild"]
932 # TODO: Move into database as above
933 if conf.FindB("Dinstall::SecurityQueueBuild"):
934 dest_dir = os.path.join(dest_dir, suitename)
936 for file_entry in changes.files.keys():
937 src = os.path.join(srcpath, file_entry)
938 dest = os.path.join(dest_dir, file_entry)
940 # TODO: Move into database as above
941 if Cnf.FindB("Dinstall::SecurityQueueBuild"):
942 # Copy it since the original won't be readable by www-data
943 utils.copy(src, dest)
945 # Create a symlink to it
946 os.symlink(src, dest)
949 qb.suite_id = s.suite_id
950 qb.queue_id = self.queue_id
956 # If the .orig.tar.gz is in the pool, create a symlink to
957 # it (if one doesn't already exist)
958 if changes.orig_tar_id:
959 # Determine the .orig.tar.gz file name
960 for dsc_file in changes.dsc_files.keys():
961 if dsc_file.endswith(".orig.tar.gz"):
964 dest = os.path.join(dest_dir, filename)
966 # If it doesn't exist, create a symlink
967 if not os.path.exists(dest):
968 q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
969 {'id': changes.orig_tar_id})
972 return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
974 src = os.path.join(res[0], res[1])
975 os.symlink(src, dest)
977 # Add it to the list of packages for later processing by apt-ftparchive
979 qb.suite_id = s.suite_id
980 qb.queue_id = self.queue_id
985 # If it does, update things to ensure it's not removed prematurely
987 qb = get_queue_build(dest, suite_id, session)
998 __all__.append('Queue')
1000 def get_queue(queuename, session=None):
1002 Returns Queue object for given C{queue name}.
1004 @type queuename: string
1005 @param queuename: The name of the queue
1007 @type session: Session
1008 @param session: Optional SQLA session object (a temporary one will be
1009 generated if not supplied)
1012 @return: Queue object for the given queue
1016 session = DBConn().session()
1017 q = session.query(Queue).filter_by(queue_name=queuename)
1022 __all__.append('get_queue')
1024 ################################################################################
1026 class QueueBuild(object):
1027 def __init__(self, *args, **kwargs):
1031 return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1033 __all__.append('QueueBuild')
1035 def get_queue_build(filename, suite_id, session=None):
1037 Returns QueueBuild object for given C{filename} and C{suite id}.
1039 @type filename: string
1040 @param filename: The name of the file
1043 @param suiteid: Suite ID
1045 @type session: Session
1046 @param session: Optional SQLA session object (a temporary one will be
1047 generated if not supplied)
1050 @return: Queue object for the given queue
1054 session = DBConn().session()
1055 q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite_id)
1060 __all__.append('get_queue_build')
1062 ################################################################################
1064 class Section(object):
1065 def __init__(self, *args, **kwargs):
1069 return '<Section %s>' % self.section
1071 __all__.append('Section')
1073 def get_section(section, session=None):
1075 Returns Section object for given C{section name}.
1077 @type section: string
1078 @param section: The name of the section
1080 @type session: Session
1081 @param session: Optional SQLA session object (a temporary one will be
1082 generated if not supplied)
1085 @return: Section object for the given section name
1089 session = DBConn().session()
1090 q = session.query(Section).filter_by(section=section)
1095 __all__.append('get_section')
1097 ################################################################################
1099 class DBSource(object):
1100 def __init__(self, *args, **kwargs):
1104 return '<DBSource %s (%s)>' % (self.source, self.version)
1106 __all__.append('DBSource')
1108 def source_exists(source, source_version, suites = ["any"], session=None):
1110 Ensure that source exists somewhere in the archive for the binary
1111 upload being processed.
1112 1. exact match => 1.0-3
1113 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1115 @type package: string
1116 @param package: package source name
1118 @type source_version: string
1119 @param source_version: expected source version
1122 @param suites: list of suites to check in, default I{any}
1124 @type session: Session
1125 @param session: Optional SQLA session object (a temporary one will be
1126 generated if not supplied)
1129 @return: returns 1 if a source with expected version is found, otherwise 0
1134 session = DBConn().session()
1138 for suite in suites:
1139 q = session.query(DBSource).filter_by(source=source)
1141 # source must exist in suite X, or in some other suite that's
1142 # mapped to X, recursively... silent-maps are counted too,
1143 # unreleased-maps aren't.
1144 maps = cnf.ValueList("SuiteMappings")[:]
1146 maps = [ m.split() for m in maps ]
1147 maps = [ (x[1], x[2]) for x in maps
1148 if x[0] == "map" or x[0] == "silent-map" ]
1151 if x[1] in s and x[0] not in s:
1154 q = q.join(SrcAssociation).join(Suite)
1155 q = q.filter(Suite.suite_name.in_(s))
1157 # Reduce the query results to a list of version numbers
1158 ql = [ j.version for j in q.all() ]
1161 if source_version in ql:
1165 from daklib.regexes import re_bin_only_nmu
1166 orig_source_version = re_bin_only_nmu.sub('', source_version)
1167 if orig_source_version in ql:
1170 # No source found so return not ok
1176 __all__.append('source_exists')
1178 def get_sources_from_name(source, dm_upload_allowed=None, session=None):
1180 Returns list of DBSource objects for given C{source} name
1183 @param source: DBSource package name to search for
1185 @type dm_upload_allowed: bool
1186 @param dm_upload_allowed: If None, no effect. If True or False, only
1187 return packages with that dm_upload_allowed setting
1189 @type session: Session
1190 @param session: Optional SQL session object (a temporary one will be
1191 generated if not supplied)
1194 @return: list of DBSource objects for the given name (may be empty)
1197 session = DBConn().session()
1199 q = session.query(DBSource).filter_by(source=source)
1200 if dm_upload_allowed is not None:
1201 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1205 __all__.append('get_sources_from_name')
1207 def get_source_in_suite(source, suite, session=None):
1209 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1211 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1212 - B{suite} - a suite name, eg. I{unstable}
1214 @type source: string
1215 @param source: source package name
1218 @param suite: the suite name
1221 @return: the version for I{source} in I{suite}
1225 session = DBConn().session()
1226 q = session.query(SrcAssociation)
1227 q = q.join('source').filter_by(source=source)
1228 q = q.join('suite').filter_by(suite_name=suite)
1231 # ???: Maybe we should just return the SrcAssociation object instead
1232 return q.one().source
1234 __all__.append('get_source_in_suite')
1236 ################################################################################
1238 class SrcAssociation(object):
1239 def __init__(self, *args, **kwargs):
1243 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1245 __all__.append('SrcAssociation')
1247 ################################################################################
1249 class SrcUploader(object):
1250 def __init__(self, *args, **kwargs):
1254 return '<SrcUploader %s>' % self.uploader_id
1256 __all__.append('SrcUploader')
1258 ################################################################################
1260 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1261 ('SuiteID', 'suite_id'),
1262 ('Version', 'version'),
1263 ('Origin', 'origin'),
1265 ('Description', 'description'),
1266 ('Untouchable', 'untouchable'),
1267 ('Announce', 'announce'),
1268 ('Codename', 'codename'),
1269 ('OverrideCodename', 'overridecodename'),
1270 ('ValidTime', 'validtime'),
1271 ('Priority', 'priority'),
1272 ('NotAutomatic', 'notautomatic'),
1273 ('CopyChanges', 'copychanges'),
1274 ('CopyDotDak', 'copydotdak'),
1275 ('CommentsDir', 'commentsdir'),
1276 ('OverrideSuite', 'overridesuite'),
1277 ('ChangelogBase', 'changelogbase')]
1280 class Suite(object):
1281 def __init__(self, *args, **kwargs):
1285 return '<Suite %s>' % self.suite_name
1289 for disp, field in SUITE_FIELDS:
1290 val = getattr(self, field, None)
1292 ret.append("%s: %s" % (disp, val))
1294 return "\n".join(ret)
1296 __all__.append('Suite')
1298 def get_suite_architecture(suite, architecture, session=None):
1300 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1304 @param suite: Suite name to search for
1306 @type architecture: str
1307 @param architecture: Architecture name to search for
1309 @type session: Session
1310 @param session: Optional SQL session object (a temporary one will be
1311 generated if not supplied)
1313 @rtype: SuiteArchitecture
1314 @return: the SuiteArchitecture object or None
1318 session = DBConn().session()
1320 q = session.query(SuiteArchitecture)
1321 q = q.join(Architecture).filter_by(arch_string=architecture)
1322 q = q.join(Suite).filter_by(suite_name=suite)
1327 __all__.append('get_suite_architecture')
1329 def get_suite(suite, session=None):
1331 Returns Suite object for given C{suite name}.
1334 @param suite: The name of the suite
1336 @type session: Session
1337 @param session: Optional SQLA session object (a temporary one will be
1338 generated if not supplied)
1341 @return: Suite object for the requested suite name (None if not presenT)
1345 session = DBConn().session()
1346 q = session.query(Suite).filter_by(suite_name=suite)
1351 __all__.append('get_suite')
1353 ################################################################################
1355 class SuiteArchitecture(object):
1356 def __init__(self, *args, **kwargs):
1360 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1362 __all__.append('SuiteArchitecture')
1364 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1366 Returns list of Architecture objects for given C{suite} name
1369 @param source: Suite name to search for
1371 @type skipsrc: boolean
1372 @param skipsrc: Whether to skip returning the 'source' architecture entry
1375 @type skipall: boolean
1376 @param skipall: Whether to skip returning the 'all' architecture entry
1379 @type session: Session
1380 @param session: Optional SQL session object (a temporary one will be
1381 generated if not supplied)
1384 @return: list of Architecture objects for the given name (may be empty)
1388 session = DBConn().session()
1390 q = session.query(Architecture)
1391 q = q.join(SuiteArchitecture)
1392 q = q.join(Suite).filter_by(suite_name=suite)
1394 q = q.filter(Architecture.arch_string != 'source')
1396 q = q.filter(Architecture.arch_string != 'all')
1397 q = q.order_by('arch_string')
1400 __all__.append('get_suite_architectures')
1402 ################################################################################
1405 def __init__(self, *args, **kwargs):
1409 return '<Uid %s (%s)>' % (self.uid, self.name)
1411 __all__.append('Uid')
1413 def add_database_user(uidname, session=None):
1415 Adds a database user
1417 @type uidname: string
1418 @param uidname: The uid of the user to add
1420 @type session: SQLAlchemy
1421 @param session: Optional SQL session object (a temporary one will be
1422 generated if not supplied). If not passed, a commit will be performed at
1423 the end of the function, otherwise the caller is responsible for commiting.
1426 @return: the uid object for the given uidname
1428 privatetrans = False
1430 session = DBConn().session()
1434 session.execute("CREATE USER :uid", {'uid': uidname})
1438 traceback.print_exc()
1441 __all__.append('add_database_user')
1443 def get_or_set_uid(uidname, session=None):
1445 Returns uid object for given uidname.
1447 If no matching uidname is found, a row is inserted.
1449 @type uidname: string
1450 @param uidname: The uid to add
1452 @type session: SQLAlchemy
1453 @param session: Optional SQL session object (a temporary one will be
1454 generated if not supplied). If not passed, a commit will be performed at
1455 the end of the function, otherwise the caller is responsible for commiting.
1458 @return: the uid object for the given uidname
1460 privatetrans = False
1462 session = DBConn().session()
1466 q = session.query(Uid).filter_by(uid=uidname)
1478 traceback.print_exc()
1481 __all__.append('get_or_set_uid')
1484 def get_uid_from_fingerprint(fpr, session=None):
1486 session = DBConn().session()
1488 q = session.query(Uid)
1489 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
1496 __all__.append('get_uid_from_fingerprint')
1498 ################################################################################
1500 class DBConn(Singleton):
1502 database module init.
1504 def __init__(self, *args, **kwargs):
1505 super(DBConn, self).__init__(*args, **kwargs)
1507 def _startup(self, *args, **kwargs):
1509 if kwargs.has_key('debug'):
1513 def __setuptables(self):
1514 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
1515 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
1516 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
1517 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
1518 self.tbl_component = Table('component', self.db_meta, autoload=True)
1519 self.tbl_config = Table('config', self.db_meta, autoload=True)
1520 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
1521 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
1522 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
1523 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
1524 self.tbl_files = Table('files', self.db_meta, autoload=True)
1525 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
1526 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
1527 self.tbl_location = Table('location', self.db_meta, autoload=True)
1528 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
1529 self.tbl_override = Table('override', self.db_meta, autoload=True)
1530 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
1531 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
1532 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
1533 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
1534 self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
1535 self.tbl_section = Table('section', self.db_meta, autoload=True)
1536 self.tbl_source = Table('source', self.db_meta, autoload=True)
1537 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
1538 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
1539 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
1540 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
1541 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
1543 def __setupmappers(self):
1544 mapper(Architecture, self.tbl_architecture,
1545 properties = dict(arch_id = self.tbl_architecture.c.id))
1547 mapper(Archive, self.tbl_archive,
1548 properties = dict(archive_id = self.tbl_archive.c.id,
1549 archive_name = self.tbl_archive.c.name))
1551 mapper(BinAssociation, self.tbl_bin_associations,
1552 properties = dict(ba_id = self.tbl_bin_associations.c.id,
1553 suite_id = self.tbl_bin_associations.c.suite,
1554 suite = relation(Suite),
1555 binary_id = self.tbl_bin_associations.c.bin,
1556 binary = relation(DBBinary)))
1558 mapper(DBBinary, self.tbl_binaries,
1559 properties = dict(binary_id = self.tbl_binaries.c.id,
1560 package = self.tbl_binaries.c.package,
1561 version = self.tbl_binaries.c.version,
1562 maintainer_id = self.tbl_binaries.c.maintainer,
1563 maintainer = relation(Maintainer),
1564 source_id = self.tbl_binaries.c.source,
1565 source = relation(DBSource),
1566 arch_id = self.tbl_binaries.c.architecture,
1567 architecture = relation(Architecture),
1568 poolfile_id = self.tbl_binaries.c.file,
1569 poolfile = relation(PoolFile),
1570 binarytype = self.tbl_binaries.c.type,
1571 fingerprint_id = self.tbl_binaries.c.sig_fpr,
1572 fingerprint = relation(Fingerprint),
1573 install_date = self.tbl_binaries.c.install_date,
1574 binassociations = relation(BinAssociation,
1575 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
1577 mapper(Component, self.tbl_component,
1578 properties = dict(component_id = self.tbl_component.c.id,
1579 component_name = self.tbl_component.c.name))
1581 mapper(DBConfig, self.tbl_config,
1582 properties = dict(config_id = self.tbl_config.c.id))
1584 mapper(ContentAssociation, self.tbl_content_associations,
1585 properties = dict(ca_id = self.tbl_content_associations.c.id,
1586 filename_id = self.tbl_content_associations.c.filename,
1587 filename = relation(ContentFilename),
1588 filepath_id = self.tbl_content_associations.c.filepath,
1589 filepath = relation(ContentFilepath),
1590 binary_id = self.tbl_content_associations.c.binary_pkg,
1591 binary = relation(DBBinary)))
1594 mapper(ContentFilename, self.tbl_content_file_names,
1595 properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
1596 filename = self.tbl_content_file_names.c.file))
1598 mapper(ContentFilepath, self.tbl_content_file_paths,
1599 properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
1600 filepath = self.tbl_content_file_paths.c.path))
1602 mapper(DSCFile, self.tbl_dsc_files,
1603 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
1604 source_id = self.tbl_dsc_files.c.source,
1605 source = relation(DBSource),
1606 poolfile_id = self.tbl_dsc_files.c.file,
1607 poolfile = relation(PoolFile)))
1609 mapper(PoolFile, self.tbl_files,
1610 properties = dict(file_id = self.tbl_files.c.id,
1611 filesize = self.tbl_files.c.size,
1612 location_id = self.tbl_files.c.location,
1613 location = relation(Location)))
1615 mapper(Fingerprint, self.tbl_fingerprint,
1616 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
1617 uid_id = self.tbl_fingerprint.c.uid,
1618 uid = relation(Uid),
1619 keyring_id = self.tbl_fingerprint.c.keyring,
1620 keyring = relation(Keyring)))
1622 mapper(Keyring, self.tbl_keyrings,
1623 properties = dict(keyring_name = self.tbl_keyrings.c.name,
1624 keyring_id = self.tbl_keyrings.c.id))
1626 mapper(Location, self.tbl_location,
1627 properties = dict(location_id = self.tbl_location.c.id,
1628 component_id = self.tbl_location.c.component,
1629 component = relation(Component),
1630 archive_id = self.tbl_location.c.archive,
1631 archive = relation(Archive),
1632 archive_type = self.tbl_location.c.type))
1634 mapper(Maintainer, self.tbl_maintainer,
1635 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
1637 mapper(Override, self.tbl_override,
1638 properties = dict(suite_id = self.tbl_override.c.suite,
1639 suite = relation(Suite),
1640 component_id = self.tbl_override.c.component,
1641 component = relation(Component),
1642 priority_id = self.tbl_override.c.priority,
1643 priority = relation(Priority),
1644 section_id = self.tbl_override.c.section,
1645 section = relation(Section),
1646 overridetype_id = self.tbl_override.c.type,
1647 overridetype = relation(OverrideType)))
1649 mapper(OverrideType, self.tbl_override_type,
1650 properties = dict(overridetype = self.tbl_override_type.c.type,
1651 overridetype_id = self.tbl_override_type.c.id))
1653 mapper(PendingContentAssociation, self.tbl_pending_content_associations,
1654 properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
1655 filepath_id = self.tbl_pending_content_associations.c.filepath,
1656 filepath = relation(ContentFilepath),
1657 filename_id = self.tbl_pending_content_associations.c.filename,
1658 filename = relation(ContentFilename)))
1660 mapper(Priority, self.tbl_priority,
1661 properties = dict(priority_id = self.tbl_priority.c.id))
1663 mapper(Queue, self.tbl_queue,
1664 properties = dict(queue_id = self.tbl_queue.c.id))
1666 mapper(QueueBuild, self.tbl_queue_build,
1667 properties = dict(suite_id = self.tbl_queue_build.c.suite,
1668 queue_id = self.tbl_queue_build.c.queue,
1669 queue = relation(Queue, backref='queuebuild')))
1671 mapper(Section, self.tbl_section,
1672 properties = dict(section_id = self.tbl_section.c.id))
1674 mapper(DBSource, self.tbl_source,
1675 properties = dict(source_id = self.tbl_source.c.id,
1676 version = self.tbl_source.c.version,
1677 maintainer_id = self.tbl_source.c.maintainer,
1678 maintainer = relation(Maintainer,
1679 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
1680 poolfile_id = self.tbl_source.c.file,
1681 poolfile = relation(PoolFile),
1682 fingerprint_id = self.tbl_source.c.sig_fpr,
1683 fingerprint = relation(Fingerprint),
1684 changedby_id = self.tbl_source.c.changedby,
1685 changedby = relation(Maintainer,
1686 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
1687 srcfiles = relation(DSCFile,
1688 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
1689 srcassociations = relation(SrcAssociation,
1690 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
1692 mapper(SrcAssociation, self.tbl_src_associations,
1693 properties = dict(sa_id = self.tbl_src_associations.c.id,
1694 suite_id = self.tbl_src_associations.c.suite,
1695 suite = relation(Suite),
1696 source_id = self.tbl_src_associations.c.source,
1697 source = relation(DBSource)))
1699 mapper(SrcUploader, self.tbl_src_uploaders,
1700 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
1701 source_id = self.tbl_src_uploaders.c.source,
1702 source = relation(DBSource,
1703 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
1704 maintainer_id = self.tbl_src_uploaders.c.maintainer,
1705 maintainer = relation(Maintainer,
1706 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
1708 mapper(Suite, self.tbl_suite,
1709 properties = dict(suite_id = self.tbl_suite.c.id))
1711 mapper(SuiteArchitecture, self.tbl_suite_architectures,
1712 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
1713 suite = relation(Suite, backref='suitearchitectures'),
1714 arch_id = self.tbl_suite_architectures.c.architecture,
1715 architecture = relation(Architecture)))
1717 mapper(Uid, self.tbl_uid,
1718 properties = dict(uid_id = self.tbl_uid.c.id,
1719 fingerprint = relation(Fingerprint)))
1721 ## Connection functions
1722 def __createconn(self):
1723 from config import Config
1727 connstr = "postgres://%s" % cnf["DB::Host"]
1728 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1729 connstr += ":%s" % cnf["DB::Port"]
1730 connstr += "/%s" % cnf["DB::Name"]
1733 connstr = "postgres:///%s" % cnf["DB::Name"]
1734 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
1735 connstr += "?port=%s" % cnf["DB::Port"]
1737 self.db_pg = create_engine(connstr, echo=self.debug)
1738 self.db_meta = MetaData()
1739 self.db_meta.bind = self.db_pg
1740 self.db_smaker = sessionmaker(bind=self.db_pg,
1744 self.__setuptables()
1745 self.__setupmappers()
1748 return self.db_smaker()
1750 __all__.append('DBConn')