X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=daklib%2Fdbconn.py;h=e1e21d2b2b1f10a386dd42c50460287d7e6c8f4d;hb=221c511735a22974ac5ee9e1d2d5d52bf29e263e;hp=baa16d0410a8095d54ec4494c58935a13b7fe95d;hpb=be11dfd4b92f3ae0aabdb952afccb8f7a6824f9f;p=dak.git diff --git a/daklib/dbconn.py b/daklib/dbconn.py index baa16d04..e1e21d2b 100755 --- a/daklib/dbconn.py +++ b/daklib/dbconn.py @@ -38,6 +38,14 @@ import re import psycopg2 import traceback import commands + +try: + # python >= 2.6 + import json +except: + # python <= 2.5 + import simplejson as json + from datetime import datetime, timedelta from errno import ENOENT from tempfile import mkstemp, mkdtemp @@ -46,7 +54,8 @@ from inspect import getargspec import sqlalchemy from sqlalchemy import create_engine, Table, MetaData, Column, Integer -from sqlalchemy.orm import sessionmaker, mapper, relation +from sqlalchemy.orm import sessionmaker, mapper, relation, object_session, \ + backref, MapperExtension, EXT_CONTINUE from sqlalchemy import types as sqltypes # Don't remove this, we re-export the exceptions to scripts which import us @@ -57,21 +66,41 @@ from sqlalchemy.orm.exc import NoResultFound # in the database from config import Config from textutils import fix_maintainer -from dak_exceptions import NoSourceFieldError +from dak_exceptions import DBUpdateError, NoSourceFieldError + +# suppress some deprecation warnings in squeeze related to sqlalchemy +import warnings +warnings.filterwarnings('ignore', \ + "The SQLAlchemy PostgreSQL dialect has been renamed from 'postgres' to 'postgresql'.*", \ + SADeprecationWarning) +# TODO: sqlalchemy needs some extra configuration to correctly reflect +# the ind_deb_contents_* indexes - we ignore the warnings at the moment +warnings.filterwarnings("ignore", 'Predicate of partial index', SAWarning) + ################################################################################ # Patch in support for the debversion field type so that it works during # reflection -class DebVersion(sqltypes.Text): - """ - Support the debversion type - """ +try: + # that is for sqlalchemy 0.6 + UserDefinedType = sqltypes.UserDefinedType +except: + # this one for sqlalchemy 0.5 + UserDefinedType = sqltypes.TypeEngine +class DebVersion(UserDefinedType): def get_col_spec(self): return "DEBVERSION" + def bind_processor(self, dialect): + return None + + # ' = None' is needed for sqlalchemy 0.5: + def result_processor(self, dialect, coltype = None): + return None + sa_major_version = sqlalchemy.__version__[0:3] if sa_major_version in ["0.5", "0.6"]: from sqlalchemy.databases import postgres @@ -81,7 +110,7 @@ else: ################################################################################ -__all__ = ['IntegrityError', 'SQLAlchemyError'] +__all__ = ['IntegrityError', 'SQLAlchemyError', 'DebVersion'] ################################################################################ @@ -136,9 +165,155 @@ __all__.append('session_wrapper') ################################################################################ -class Architecture(object): - def __init__(self, *args, **kwargs): - pass +class ORMObject(object): + """ + ORMObject is a base class for all ORM classes mapped by SQLalchemy. All + derived classes must implement the properties() method. + """ + + def properties(self): + ''' + This method should be implemented by all derived classes and returns a + list of the important properties. The properties 'created' and + 'modified' will be added automatically. A suffix '_count' should be + added to properties that are lists or query objects. The most important + property name should be returned as the first element in the list + because it is used by repr(). + ''' + return [] + + def json(self): + ''' + Returns a JSON representation of the object based on the properties + returned from the properties() method. + ''' + data = {} + # add created and modified + all_properties = self.properties() + ['created', 'modified'] + for property in all_properties: + # check for list or query + if property[-6:] == '_count': + real_property = property[:-6] + if not hasattr(self, real_property): + continue + value = getattr(self, real_property) + if hasattr(value, '__len__'): + # list + value = len(value) + elif hasattr(value, 'count'): + # query + value = value.count() + else: + raise KeyError('Do not understand property %s.' % property) + else: + if not hasattr(self, property): + continue + # plain object + value = getattr(self, property) + if value is None: + # skip None + continue + elif isinstance(value, ORMObject): + # use repr() for ORMObject types + value = repr(value) + else: + # we want a string for all other types because json cannot + # encode everything + value = str(value) + data[property] = value + return json.dumps(data) + + def classname(self): + ''' + Returns the name of the class. + ''' + return type(self).__name__ + + def __repr__(self): + ''' + Returns a short string representation of the object using the first + element from the properties() method. + ''' + primary_property = self.properties()[0] + value = getattr(self, primary_property) + return '<%s %s>' % (self.classname(), str(value)) + + def __str__(self): + ''' + Returns a human readable form of the object using the properties() + method. + ''' + return '<%s %s>' % (self.classname(), self.json()) + + def not_null_constraints(self): + ''' + Returns a list of properties that must be not NULL. Derived classes + should override this method if needed. + ''' + return [] + + validation_message = \ + "Validation failed because property '%s' must not be empty in object\n%s" + + def validate(self): + ''' + This function validates the not NULL constraints as returned by + not_null_constraints(). It raises the DBUpdateError exception if + validation fails. + ''' + for property in self.not_null_constraints(): + # TODO: It is a bit awkward that the mapper configuration allow + # directly setting the numeric _id columns. We should get rid of it + # in the long run. + if hasattr(self, property + '_id') and \ + getattr(self, property + '_id') is not None: + continue + if not hasattr(self, property) or getattr(self, property) is None: + raise DBUpdateError(self.validation_message % \ + (property, str(self))) + + @classmethod + @session_wrapper + def get(cls, primary_key, session = None): + ''' + This is a support function that allows getting an object by its primary + key. + + Architecture.get(3[, session]) + + instead of the more verbose + + session.query(Architecture).get(3) + ''' + return session.query(cls).get(primary_key) + +__all__.append('ORMObject') + +################################################################################ + +class Validator(MapperExtension): + ''' + This class calls the validate() method for each instance for the + 'before_update' and 'before_insert' events. A global object validator is + used for configuring the individual mappers. + ''' + + def before_update(self, mapper, connection, instance): + instance.validate() + return EXT_CONTINUE + + def before_insert(self, mapper, connection, instance): + instance.validate() + return EXT_CONTINUE + +validator = Validator() + +################################################################################ + +class Architecture(ORMObject): + def __init__(self, arch_string = None, description = None): + self.arch_string = arch_string + self.description = description def __eq__(self, val): if isinstance(val, str): @@ -152,8 +327,11 @@ class Architecture(object): # This signals to use the normal comparison operator return NotImplemented - def __repr__(self): - return '' % self.arch_string + def properties(self): + return ['arch_string', 'arch_id', 'suites_count'] + + def not_null_constraints(self): + return ['arch_string'] __all__.append('Architecture') @@ -182,6 +360,7 @@ def get_architecture(architecture, session=None): __all__.append('get_architecture') +# TODO: should be removed because the implementation is too trivial @session_wrapper def get_architecture_suites(architecture, session=None): """ @@ -198,13 +377,7 @@ def get_architecture_suites(architecture, session=None): @return: list of Suite objects for the given name (may be empty) """ - q = session.query(Suite) - q = q.join(SuiteArchitecture) - q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name') - - ret = q.all() - - return ret + return get_architecture(architecture, session).suites __all__.append('get_architecture_suites') @@ -248,17 +421,6 @@ __all__.append('get_archive') ################################################################################ -class BinAssociation(object): - def __init__(self, *args, **kwargs): - pass - - def __repr__(self): - return '' % (self.ba_id, self.binary, self.suite) - -__all__.append('BinAssociation') - -################################################################################ - class BinContents(object): def __init__(self, *args, **kwargs): pass @@ -270,12 +432,26 @@ __all__.append('BinContents') ################################################################################ -class DBBinary(object): - def __init__(self, *args, **kwargs): - pass - - def __repr__(self): - return '' % (self.package, self.version, self.architecture) +class DBBinary(ORMObject): + def __init__(self, package = None, source = None, version = None, \ + maintainer = None, architecture = None, poolfile = None, \ + binarytype = 'deb'): + self.package = package + self.source = source + self.version = version + self.maintainer = maintainer + self.architecture = architecture + self.poolfile = poolfile + self.binarytype = binarytype + + def properties(self): + return ['package', 'version', 'maintainer', 'source', 'architecture', \ + 'poolfile', 'binarytype', 'fingerprint', 'install_date', \ + 'suites_count', 'binary_id'] + + def not_null_constraints(self): + return ['package', 'version', 'maintainer', 'source', 'poolfile', \ + 'binarytype'] __all__.append('DBBinary') @@ -291,93 +467,10 @@ def get_suites_binary_in(package, session=None): @return: list of Suite objects for the given package """ - return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all() + return session.query(Suite).filter(Suite.binaries.any(DBBinary.package == package)).all() __all__.append('get_suites_binary_in') -@session_wrapper -def get_binary_from_id(binary_id, session=None): - """ - Returns DBBinary object for given C{id} - - @type binary_id: int - @param binary_id: Id of the required binary - - @type session: Session - @param session: Optional SQLA session object (a temporary one will be - generated if not supplied) - - @rtype: DBBinary - @return: DBBinary object for the given binary (None if not present) - """ - - q = session.query(DBBinary).filter_by(binary_id=binary_id) - - try: - return q.one() - except NoResultFound: - return None - -__all__.append('get_binary_from_id') - -@session_wrapper -def get_binaries_from_name(package, version=None, architecture=None, session=None): - """ - Returns list of DBBinary objects for given C{package} name - - @type package: str - @param package: DBBinary package name to search for - - @type version: str or None - @param version: Version to search for (or None) - - @type architecture: str, list or None - @param architecture: Architectures to limit to (or None if no limit) - - @type session: Session - @param session: Optional SQL session object (a temporary one will be - generated if not supplied) - - @rtype: list - @return: list of DBBinary objects for the given name (may be empty) - """ - - q = session.query(DBBinary).filter_by(package=package) - - if version is not None: - q = q.filter_by(version=version) - - if architecture is not None: - if not isinstance(architecture, list): - architecture = [architecture] - q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture)) - - ret = q.all() - - return ret - -__all__.append('get_binaries_from_name') - -@session_wrapper -def get_binaries_from_source_id(source_id, session=None): - """ - Returns list of DBBinary objects for given C{source_id} - - @type source_id: int - @param source_id: source_id to search for - - @type session: Session - @param session: Optional SQL session object (a temporary one will be - generated if not supplied) - - @rtype: list - @return: list of DBBinary objects for the given name (may be empty) - """ - - return session.query(DBBinary).filter_by(source_id=source_id).all() - -__all__.append('get_binaries_from_source_id') - @session_wrapper def get_binary_from_name_suite(package, suitename, session=None): ### For dak examine-package @@ -756,9 +849,9 @@ __all__.append('ChangePendingSource') ################################################################################ -class Component(object): - def __init__(self, *args, **kwargs): - pass +class Component(ORMObject): + def __init__(self, component_name = None): + self.component_name = component_name def __eq__(self, val): if isinstance(val, str): @@ -772,8 +865,12 @@ class Component(object): # This signals to use the normal comparison operator return NotImplemented - def __repr__(self): - return '' % self.component_name + def properties(self): + return ['component_name', 'component_id', 'description', 'location', \ + 'meets_dfsg'] + + def not_null_constraints(self): + return ['component_name'] __all__.append('Component') @@ -1053,24 +1150,35 @@ __all__.append('get_dscfiles') ################################################################################ -class PoolFile(object): - def __init__(self, *args, **kwargs): - pass - - def __repr__(self): - return '' % self.filename +class PoolFile(ORMObject): + def __init__(self, filename = None, location = None, filesize = -1, \ + md5sum = None): + self.filename = filename + self.location = location + self.filesize = filesize + self.md5sum = md5sum @property def fullpath(self): return os.path.join(self.location.path, self.filename) + def is_valid(self, filesize = -1, md5sum = None):\ + return self.filesize == filesize and self.md5sum == md5sum + + def properties(self): + return ['filename', 'file_id', 'filesize', 'md5sum', 'sha1sum', \ + 'sha256sum', 'location', 'source', 'binary', 'last_used'] + + def not_null_constraints(self): + return ['filename', 'md5sum', 'location'] + __all__.append('PoolFile') @session_wrapper def check_poolfile(filename, filesize, md5sum, location_id, session=None): """ Returns a tuple: - (ValidFileFound [boolean or None], PoolFile object or None) + (ValidFileFound [boolean], PoolFile object or None) @type filename: string @param filename: the filename of the file to check against the DB @@ -1086,34 +1194,24 @@ def check_poolfile(filename, filesize, md5sum, location_id, session=None): @rtype: tuple @return: Tuple of length 2. - - If more than one file found with that name: (C{None}, C{None}) - If valid pool file found: (C{True}, C{PoolFile object}) - If valid pool file not found: - (C{False}, C{None}) if no file found - (C{False}, C{PoolFile object}) if file found with size/md5sum mismatch """ - q = session.query(PoolFile).filter_by(filename=filename) - q = q.join(Location).filter_by(location_id=location_id) + poolfile = session.query(Location).get(location_id). \ + files.filter_by(filename=filename).first() + valid = False + if poolfile and poolfile.is_valid(filesize = filesize, md5sum = md5sum): + valid = True - ret = None - - if q.count() > 1: - ret = (None, None) - elif q.count() < 1: - ret = (False, None) - else: - obj = q.one() - if obj.md5sum != md5sum or obj.filesize != int(filesize): - ret = (False, obj) - - if ret is None: - ret = (True, obj) - - return ret + return (valid, poolfile) __all__.append('check_poolfile') +# TODO: the implementation can trivially be inlined at the place where the +# function is called @session_wrapper def get_poolfile_by_id(file_id, session=None): """ @@ -1126,41 +1224,10 @@ def get_poolfile_by_id(file_id, session=None): @return: either the PoolFile object or None """ - q = session.query(PoolFile).filter_by(file_id=file_id) - - try: - return q.one() - except NoResultFound: - return None + return session.query(PoolFile).get(file_id) __all__.append('get_poolfile_by_id') - -@session_wrapper -def get_poolfile_by_name(filename, location_id=None, session=None): - """ - Returns an array of PoolFile objects for the given filename and - (optionally) location_id - - @type filename: string - @param filename: the filename of the file to check against the DB - - @type location_id: int - @param location_id: the id of the location to look in (optional) - - @rtype: array - @return: array of PoolFile objects - """ - - q = session.query(PoolFile).filter_by(filename=filename) - - if location_id is not None: - q = q.join(Location).filter_by(location_id=location_id) - - return q.all() - -__all__.append('get_poolfile_by_name') - @session_wrapper def get_poolfile_like_name(filename, session=None): """ @@ -1215,12 +1282,16 @@ __all__.append('add_poolfile') ################################################################################ -class Fingerprint(object): - def __init__(self, *args, **kwargs): - pass +class Fingerprint(ORMObject): + def __init__(self, fingerprint = None): + self.fingerprint = fingerprint - def __repr__(self): - return '' % self.fingerprint + def properties(self): + return ['fingerprint', 'fingerprint_id', 'keyring', 'uid', \ + 'binary_reject'] + + def not_null_constraints(self): + return ['fingerprint'] __all__.append('Fingerprint') @@ -1505,12 +1576,20 @@ __all__.append('get_dbchange') ################################################################################ -class Location(object): - def __init__(self, *args, **kwargs): - pass +# TODO: Why do we have a separate Location class? Can't it be fully integrated +# into class Component? +class Location(ORMObject): + def __init__(self, path = None, component = None): + self.path = path + self.component = component + # the column 'type' should go away, see comment at mapper + self.archive_type = 'pool' - def __repr__(self): - return '' % (self.path, self.location_id) + def properties(self): + return ['path', 'archive_type', 'component', 'files_count'] + + def not_null_constraints(self): + return ['path', 'archive_type'] __all__.append('Location') @@ -1550,12 +1629,15 @@ __all__.append('get_location') ################################################################################ -class Maintainer(object): - def __init__(self, *args, **kwargs): - pass +class Maintainer(ORMObject): + def __init__(self, name = None): + self.name = name - def __repr__(self): - return '''''' % (self.name, self.maintainer_id) + def properties(self): + return ['name', 'maintainer_id'] + + def not_null_constraints(self): + return ['name'] def get_split_maintainer(self): if not hasattr(self, 'name') or self.name is None: @@ -2089,12 +2171,24 @@ __all__.append('get_sections') ################################################################################ -class DBSource(object): - def __init__(self, *args, **kwargs): - pass - - def __repr__(self): - return '' % (self.source, self.version) +class DBSource(ORMObject): + def __init__(self, source = None, version = None, maintainer = None, \ + changedby = None, poolfile = None, install_date = None): + self.source = source + self.version = version + self.maintainer = maintainer + self.changedby = changedby + self.poolfile = poolfile + self.install_date = install_date + + def properties(self): + return ['source', 'source_id', 'maintainer', 'changedby', \ + 'fingerprint', 'poolfile', 'version', 'suites_count', \ + 'install_date', 'binaries_count'] + + def not_null_constraints(self): + return ['source', 'version', 'install_date', 'maintainer', \ + 'changedby', 'poolfile', 'install_date'] __all__.append('DBSource') @@ -2125,10 +2219,14 @@ def source_exists(source, source_version, suites = ["any"], session=None): """ cnf = Config() - ret = 1 + ret = True + + from daklib.regexes import re_bin_only_nmu + orig_source_version = re_bin_only_nmu.sub('', source_version) for suite in suites: - q = session.query(DBSource).filter_by(source=source) + q = session.query(DBSource).filter_by(source=source). \ + filter(DBSource.version.in_([source_version, orig_source_version])) if suite != "any": # source must exist in suite X, or in some other suite that's # mapped to X, recursively... silent-maps are counted too, @@ -2143,24 +2241,13 @@ def source_exists(source, source_version, suites = ["any"], session=None): if x[1] in s and x[0] not in s: s.append(x[0]) - q = q.join(SrcAssociation).join(Suite) - q = q.filter(Suite.suite_name.in_(s)) + q = q.filter(DBSource.suites.any(Suite.suite_name.in_(s))) - # Reduce the query results to a list of version numbers - ql = [ j.version for j in q.all() ] - - # Try (1) - if source_version in ql: - continue - - # Try (2) - from daklib.regexes import re_bin_only_nmu - orig_source_version = re_bin_only_nmu.sub('', source_version) - if orig_source_version in ql: + if q.count() > 0: continue # No source found so return not ok - ret = 0 + ret = False return ret @@ -2178,7 +2265,7 @@ def get_suites_source_in(source, session=None): @return: list of Suite objects for the given source """ - return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all() + return session.query(Suite).filter(Suite.sources.any(source=source)).all() __all__.append('get_suites_source_in') @@ -2217,10 +2304,12 @@ def get_sources_from_name(source, version=None, dm_upload_allowed=None, session= __all__.append('get_sources_from_name') +# FIXME: This function fails badly if it finds more than 1 source package and +# its implementation is trivial enough to be inlined. @session_wrapper def get_source_in_suite(source, suite, session=None): """ - Returns list of DBSource objects for a combination of C{source} and C{suite}. + Returns a DBSource object for a combination of C{source} and C{suite}. - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc} - B{suite} - a suite name, eg. I{unstable} @@ -2236,12 +2325,9 @@ def get_source_in_suite(source, suite, session=None): """ - q = session.query(SrcAssociation) - q = q.join('source').filter_by(source=source) - q = q.join('suite').filter_by(suite_name=suite) - + q = get_suite(suite, session).get_sources(source) try: - return q.one().source + return q.one() except NoResultFound: return None @@ -2277,15 +2363,10 @@ def add_dsc_to_db(u, filename, session=None): source.poolfile_id = entry["files id"] session.add(source) - session.flush() - - for suite_name in u.pkg.changes["distribution"].keys(): - sa = SrcAssociation() - sa.source_id = source.source_id - sa.suite_id = get_suite(suite_name).suite_id - session.add(sa) - session.flush() + suite_names = u.pkg.changes["distribution"].keys() + source.suites = session.query(Suite). \ + filter(Suite.suite_name.in_(suite_names)).all() # Add the source files to the DB (files and dsc_files) dscfile = DSCFile() @@ -2335,8 +2416,6 @@ def add_dsc_to_db(u, filename, session=None): df.poolfile_id = files_id session.add(df) - session.flush() - # Add the src_uploaders to the DB uploader_ids = [source.maintainer_id] if u.pkg.dsc.has_key("uploaders"): @@ -2406,14 +2485,10 @@ def add_deb_to_db(u, filename, session=None): # Add and flush object so it has an ID session.add(bin) - session.flush() - # Add BinAssociations - for suite_name in u.pkg.changes["distribution"].keys(): - ba = BinAssociation() - ba.binary_id = bin.binary_id - ba.suite_id = get_suite(suite_name).suite_id - session.add(ba) + suite_names = u.pkg.changes["distribution"].keys() + bin.suites = session.query(Suite). \ + filter(Suite.suite_name.in_(suite_names)).all() session.flush() @@ -2441,17 +2516,6 @@ __all__.append('SourceACL') ################################################################################ -class SrcAssociation(object): - def __init__(self, *args, **kwargs): - pass - - def __repr__(self): - return '' % (self.sa_id, self.source, self.suite) - -__all__.append('SrcAssociation') - -################################################################################ - class SrcFormat(object): def __init__(self, *args, **kwargs): pass @@ -2490,12 +2554,18 @@ SUITE_FIELDS = [ ('SuiteName', 'suite_name'), ('CopyChanges', 'copychanges'), ('OverrideSuite', 'overridesuite')] -class Suite(object): - def __init__(self, *args, **kwargs): - pass +# Why the heck don't we have any UNIQUE constraints in table suite? +# TODO: Add UNIQUE constraints for appropriate columns. +class Suite(ORMObject): + def __init__(self, suite_name = None, version = None): + self.suite_name = suite_name + self.version = version - def __repr__(self): - return '' % self.suite_name + def properties(self): + return ['suite_name', 'version', 'sources_count', 'binaries_count'] + + def not_null_constraints(self): + return ['suite_name', 'version'] def __eq__(self, val): if isinstance(val, str): @@ -2518,38 +2588,48 @@ class Suite(object): return "\n".join(ret) -__all__.append('Suite') + def get_architectures(self, skipsrc=False, skipall=False): + """ + Returns list of Architecture objects -@session_wrapper -def get_suite_architecture(suite, architecture, session=None): - """ - Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it - doesn't exist + @type skipsrc: boolean + @param skipsrc: Whether to skip returning the 'source' architecture entry + (Default False) - @type suite: str - @param suite: Suite name to search for + @type skipall: boolean + @param skipall: Whether to skip returning the 'all' architecture entry + (Default False) - @type architecture: str - @param architecture: Architecture name to search for + @rtype: list + @return: list of Architecture objects for the given name (may be empty) + """ - @type session: Session - @param session: Optional SQL session object (a temporary one will be - generated if not supplied) + q = object_session(self).query(Architecture).with_parent(self) + if skipsrc: + q = q.filter(Architecture.arch_string != 'source') + if skipall: + q = q.filter(Architecture.arch_string != 'all') + return q.order_by(Architecture.arch_string).all() - @rtype: SuiteArchitecture - @return: the SuiteArchitecture object or None - """ + def get_sources(self, source): + """ + Returns a query object representing DBSource that is part of C{suite}. - q = session.query(SuiteArchitecture) - q = q.join(Architecture).filter_by(arch_string=architecture) - q = q.join(Suite).filter_by(suite_name=suite) + - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc} - try: - return q.one() - except NoResultFound: - return None + @type source: string + @param source: source package name + + @rtype: sqlalchemy.orm.query.Query + @return: a query of DBSource + + """ -__all__.append('get_suite_architecture') + session = object_session(self) + return session.query(DBSource).filter_by(source = source). \ + with_parent(self) + +__all__.append('Suite') @session_wrapper def get_suite(suite, session=None): @@ -2578,15 +2658,7 @@ __all__.append('get_suite') ################################################################################ -class SuiteArchitecture(object): - def __init__(self, *args, **kwargs): - pass - - def __repr__(self): - return '' % (self.suite_id, self.arch_id) - -__all__.append('SuiteArchitecture') - +# TODO: should be removed because the implementation is too trivial @session_wrapper def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None): """ @@ -2611,19 +2683,7 @@ def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None): @return: list of Architecture objects for the given name (may be empty) """ - q = session.query(Architecture) - q = q.join(SuiteArchitecture) - q = q.join(Suite).filter_by(suite_name=suite) - - if skipsrc: - q = q.filter(Architecture.arch_string != 'source') - - if skipall: - q = q.filter(Architecture.arch_string != 'all') - - q = q.order_by('arch_string') - - return q.all() + return get_suite(suite, session).get_architectures(skipsrc, skipall) __all__.append('get_suite_architectures') @@ -2665,9 +2725,10 @@ __all__.append('get_suite_src_formats') ################################################################################ -class Uid(object): - def __init__(self, *args, **kwargs): - pass +class Uid(ORMObject): + def __init__(self, uid = None, name = None): + self.uid = uid + self.name = name def __eq__(self, val): if isinstance(val, str): @@ -2681,8 +2742,11 @@ class Uid(object): # This signals to use the normal comparison operator return NotImplemented - def __repr__(self): - return '' % (self.uid, self.name) + def properties(self): + return ['uid', 'name', 'fingerprint'] + + def not_null_constraints(self): + return ['uid'] __all__.append('Uid') @@ -2768,7 +2832,6 @@ class DBConn(object): 'binary_acl', 'binary_acl_map', 'build_queue', - 'build_queue_files', 'changelogs_text', 'component', 'config', @@ -2779,7 +2842,6 @@ class DBConn(object): 'files', 'fingerprint', 'keyrings', - 'changes', 'keyring_acl_map', 'location', 'maintainer', @@ -2797,6 +2859,11 @@ class DBConn(object): 'suite', 'uid', 'upload_blocks', + # The following tables have primary keys but sqlalchemy + # version 0.5 fails to reflect them correctly with database + # versions before upgrade #41. + #'changes', + #'build_queue_files', ) tables_no_primary = ( @@ -2810,6 +2877,9 @@ class DBConn(object): 'suite_src_formats', 'suite_build_queue_copy', 'udeb_contents', + # see the comment above + 'changes', + 'build_queue_files', ) views = ( @@ -2836,9 +2906,9 @@ class DBConn(object): 'suite_arch_by_name', ) - # Sqlalchemy fails to reflect the SERIAL type correctly and that - # is why we have to use a workaround. It can be removed as soon - # as we switch to version 0.6. + # Sqlalchemy version 0.5 fails to reflect the SERIAL type + # correctly and that is why we have to use a workaround. It can + # be removed as soon as we switch to version 0.6. for table_name in tables_with_primary: table = Table(table_name, self.db_meta, \ Column('id', Integer, primary_key = True), \ @@ -2855,19 +2925,16 @@ class DBConn(object): def __setupmappers(self): mapper(Architecture, self.tbl_architecture, - properties = dict(arch_id = self.tbl_architecture.c.id)) + properties = dict(arch_id = self.tbl_architecture.c.id, + suites = relation(Suite, secondary=self.tbl_suite_architectures, + order_by='suite_name', + backref=backref('architectures', order_by='arch_string'))), + extension = validator) mapper(Archive, self.tbl_archive, properties = dict(archive_id = self.tbl_archive.c.id, archive_name = self.tbl_archive.c.name)) - mapper(BinAssociation, self.tbl_bin_associations, - properties = dict(ba_id = self.tbl_bin_associations.c.id, - suite_id = self.tbl_bin_associations.c.suite, - suite = relation(Suite), - binary_id = self.tbl_bin_associations.c.bin, - binary = relation(DBBinary))) - mapper(PendingBinContents, self.tbl_pending_bin_contents, properties = dict(contents_id =self.tbl_pending_bin_contents.c.id, filename = self.tbl_pending_bin_contents.c.filename, @@ -2906,17 +2973,18 @@ class DBConn(object): maintainer_id = self.tbl_binaries.c.maintainer, maintainer = relation(Maintainer), source_id = self.tbl_binaries.c.source, - source = relation(DBSource), + source = relation(DBSource, backref='binaries'), arch_id = self.tbl_binaries.c.architecture, architecture = relation(Architecture), poolfile_id = self.tbl_binaries.c.file, - poolfile = relation(PoolFile), + poolfile = relation(PoolFile, backref=backref('binary', uselist = False)), binarytype = self.tbl_binaries.c.type, fingerprint_id = self.tbl_binaries.c.sig_fpr, fingerprint = relation(Fingerprint), install_date = self.tbl_binaries.c.install_date, - binassociations = relation(BinAssociation, - primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin)))) + suites = relation(Suite, secondary=self.tbl_bin_associations, + backref=backref('binaries', lazy='dynamic'))), + extension = validator) mapper(BinaryACL, self.tbl_binary_acl, properties = dict(binary_acl_id = self.tbl_binary_acl.c.id)) @@ -2928,7 +2996,8 @@ class DBConn(object): mapper(Component, self.tbl_component, properties = dict(component_id = self.tbl_component.c.id, - component_name = self.tbl_component.c.name)) + component_name = self.tbl_component.c.name), + extension = validator) mapper(DBConfig, self.tbl_config, properties = dict(config_id = self.tbl_config.c.id)) @@ -2944,7 +3013,12 @@ class DBConn(object): properties = dict(file_id = self.tbl_files.c.id, filesize = self.tbl_files.c.size, location_id = self.tbl_files.c.location, - location = relation(Location))) + location = relation(Location, + # using lazy='dynamic' in the back + # reference because we have A LOT of + # files in one location + backref=backref('files', lazy='dynamic'))), + extension = validator) mapper(Fingerprint, self.tbl_fingerprint, properties = dict(fingerprint_id = self.tbl_fingerprint.c.id, @@ -2953,7 +3027,8 @@ class DBConn(object): keyring_id = self.tbl_fingerprint.c.keyring, keyring = relation(Keyring), source_acl = relation(SourceACL), - binary_acl = relation(BinaryACL))) + binary_acl = relation(BinaryACL)), + extension = validator) mapper(Keyring, self.tbl_keyrings, properties = dict(keyring_name = self.tbl_keyrings.c.name, @@ -3014,13 +3089,22 @@ class DBConn(object): mapper(Location, self.tbl_location, properties = dict(location_id = self.tbl_location.c.id, component_id = self.tbl_location.c.component, - component = relation(Component), + component = relation(Component, \ + backref=backref('location', uselist = False)), archive_id = self.tbl_location.c.archive, archive = relation(Archive), - archive_type = self.tbl_location.c.type)) + # FIXME: the 'type' column is old cruft and + # should be removed in the future. + archive_type = self.tbl_location.c.type), + extension = validator) mapper(Maintainer, self.tbl_maintainer, - properties = dict(maintainer_id = self.tbl_maintainer.c.id)) + properties = dict(maintainer_id = self.tbl_maintainer.c.id, + maintains_sources = relation(DBSource, backref='maintainer', + primaryjoin=(self.tbl_maintainer.c.id==self.tbl_source.c.maintainer)), + changed_sources = relation(DBSource, backref='changedby', + primaryjoin=(self.tbl_maintainer.c.id==self.tbl_source.c.changedby))), + extension = validator) mapper(NewComment, self.tbl_new_comments, properties = dict(comment_id = self.tbl_new_comments.c.id)) @@ -3056,31 +3140,21 @@ class DBConn(object): properties = dict(source_id = self.tbl_source.c.id, version = self.tbl_source.c.version, maintainer_id = self.tbl_source.c.maintainer, - maintainer = relation(Maintainer, - primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)), poolfile_id = self.tbl_source.c.file, - poolfile = relation(PoolFile), + poolfile = relation(PoolFile, backref=backref('source', uselist = False)), fingerprint_id = self.tbl_source.c.sig_fpr, fingerprint = relation(Fingerprint), changedby_id = self.tbl_source.c.changedby, - changedby = relation(Maintainer, - primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)), srcfiles = relation(DSCFile, primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)), - srcassociations = relation(SrcAssociation, - primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)), - srcuploaders = relation(SrcUploader))) + suites = relation(Suite, secondary=self.tbl_src_associations, + backref=backref('sources', lazy='dynamic')), + srcuploaders = relation(SrcUploader)), + extension = validator) mapper(SourceACL, self.tbl_source_acl, properties = dict(source_acl_id = self.tbl_source_acl.c.id)) - mapper(SrcAssociation, self.tbl_src_associations, - properties = dict(sa_id = self.tbl_src_associations.c.id, - suite_id = self.tbl_src_associations.c.suite, - suite = relation(Suite), - source_id = self.tbl_src_associations.c.source, - source = relation(DBSource))) - mapper(SrcFormat, self.tbl_src_format, properties = dict(src_format_id = self.tbl_src_format.c.id, format_name = self.tbl_src_format.c.format_name)) @@ -3097,13 +3171,9 @@ class DBConn(object): mapper(Suite, self.tbl_suite, properties = dict(suite_id = self.tbl_suite.c.id, policy_queue = relation(PolicyQueue), - copy_queues = relation(BuildQueue, secondary=self.tbl_suite_build_queue_copy))) - - mapper(SuiteArchitecture, self.tbl_suite_architectures, - properties = dict(suite_id = self.tbl_suite_architectures.c.suite, - suite = relation(Suite, backref='suitearchitectures'), - arch_id = self.tbl_suite_architectures.c.architecture, - architecture = relation(Architecture))) + copy_queues = relation(BuildQueue, + secondary=self.tbl_suite_build_queue_copy)), + extension = validator) mapper(SuiteSrcFormat, self.tbl_suite_src_formats, properties = dict(suite_id = self.tbl_suite_src_formats.c.suite, @@ -3113,7 +3183,8 @@ class DBConn(object): mapper(Uid, self.tbl_uid, properties = dict(uid_id = self.tbl_uid.c.id, - fingerprint = relation(Fingerprint))) + fingerprint = relation(Fingerprint)), + extension = validator) mapper(UploadBlock, self.tbl_upload_blocks, properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,