X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=daklib%2Fdbconn.py;h=6e1184271ad02ef7493178515d8bf4afbce2a2cf;hb=b67d98ef5542c0258021b03a015c775a6db66d56;hp=c290b60f84a8f0c9f789c72c3843e7af58db516f;hpb=b58774e49de73e6ec962720cc2d86c0002036929;p=dak.git diff --git a/daklib/dbconn.py b/daklib/dbconn.py index c290b60f..6e118427 100755 --- a/daklib/dbconn.py +++ b/daklib/dbconn.py @@ -37,540 +37,1380 @@ import os import psycopg2 import traceback +from sqlalchemy import create_engine, Table, MetaData, select +from sqlalchemy.orm import sessionmaker, mapper, relation + +# Don't remove this, we re-export the exceptions to scripts which import us +from sqlalchemy.exc import * + from singleton import Singleton -from config import Config +from textutils import fix_maintainer ################################################################################ -class Cache(object): - def __init__(self, hashfunc=None): - if hashfunc: - self.hashfunc = hashfunc - else: - self.hashfunc = lambda x: x['value'] +__all__ = ['IntegrityError', 'SQLAlchemyError'] + +################################################################################ + +class Architecture(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % self.arch_string + +__all__.append('Architecture') + +def get_architecture(architecture, session=None): + """ + Returns database id for given C{architecture}. - self.data = {} + @type architecture: string + @param architecture: The name of the architecture - def SetValue(self, keys, value): - self.data[self.hashfunc(keys)] = value + @type session: Session + @param session: Optional SQLA session object (a temporary one will be + generated if not supplied) + + @rtype: Architecture + @return: Architecture object for the given arch (None if not present) + + """ + if session is None: + session = DBConn().session() + q = session.query(Architecture).filter_by(arch_string=architecture) + if q.count() == 0: + return None + return q.one() + +__all__.append('get_architecture') + +def get_architecture_suites(architecture, session=None): + """ + Returns list of Suite objects for given C{architecture} name + + @type source: str + @param source: Architecture name 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 Suite objects for the given name (may be empty) + """ - def GetValue(self, keys): - return self.data.get(self.hashfunc(keys)) + if session is None: + session = DBConn().session() + + q = session.query(Suite) + q = q.join(SuiteArchitecture) + q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name') + return q.all() + +__all__.append('get_architecture_suites') ################################################################################ -class DBConn(Singleton): +class Archive(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % self.name + +__all__.append('Archive') + +def get_archive(archive, session=None): """ - database module init. + returns database id for given c{archive}. + + @type archive: string + @param archive: the name of the arhive + + @type session: Session + @param session: Optional SQLA session object (a temporary one will be + generated if not supplied) + + @rtype: Archive + @return: Archive object for the given name (None if not present) + """ + archive = archive.lower() + if session is None: + session = DBConn().session() + q = session.query(Archive).filter_by(archive_name=archive) + if q.count() == 0: + return None + return q.one() + +__all__.append('get_archive') + +################################################################################ + +class BinAssociation(object): def __init__(self, *args, **kwargs): - super(DBConn, self).__init__(*args, **kwargs) + pass - def _startup(self, *args, **kwargs): - self.__createconn() - self.__init_caches() + def __repr__(self): + return '' % (self.ba_id, self.binary, self.suite) - ## Connection functions - def __createconn(self): - cnf = Config() - connstr = "dbname=%s" % cnf["DB::Name"] - if cnf["DB::Host"]: - connstr += " host=%s" % cnf["DB::Host"] - if cnf["DB::Port"] and cnf["DB::Port"] != "-1": - connstr += " port=%s" % cnf["DB::Port"] +__all__.append('BinAssociation') + +################################################################################ - self.db_con = psycopg2.connect(connstr) +class DBBinary(object): + def __init__(self, *args, **kwargs): + pass - def reconnect(self): - try: - self.db_con.close() - except psycopg2.InterfaceError: - pass + def __repr__(self): + return '' % (self.package, self.version, self.architecture) - self.db_con = None - self.__createconn() +__all__.append('DBBinary') + +def get_binary_from_id(id, session=None): + """ + Returns DBBinary object for given C{id} + + @type id: int + @param id: Id of the required binary - ## Cache functions - def __init_caches(self): - self.caches = {'suite': Cache(), - 'section': Cache(), - 'priority': Cache(), - 'override_type': Cache(), - 'architecture': Cache(), - 'archive': Cache(), - 'component': Cache(), - 'content_path_names': Cache(), - 'content_file_names': Cache(), - 'location': Cache(lambda x: '%s_%s_%s' % (x['location'], x['component'], x['location'])), - 'maintainer': {}, # TODO - 'keyring': {}, # TODO - 'source': Cache(lambda x: '%s_%s_' % (x['source'], x['version'])), - 'files': Cache(lambda x: '%s_%s_' % (x['filename'], x['location'])), - 'maintainer': {}, # TODO - 'fingerprint': {}, # TODO - 'queue': {}, # TODO - 'uid': {}, # TODO - 'suite_version': Cache(lambda x: '%s_%s' % (x['source'], x['suite'])), - } - - self.prepared_statements = {} - - def prepare(self,name,statement): - if not self.prepared_statements.has_key(name): - c = self.cursor() - c.execute(statement) - self.prepared_statements[name] = statement - - def clear_caches(self): - self.__init_caches() + @type session: Session + @param session: Optional SQLA session object (a temporary one will be + generated if not supplied) - ## Functions to pass through to the database connector - def cursor(self): - return self.db_con.cursor() + @rtype: DBBinary + @return: DBBinary object for the given binary (None if not present) + """ + if session is None: + session = DBConn().session() + q = session.query(DBBinary).filter_by(binary_id=id) + if q.count() == 0: + return None + return q.one() + +__all__.append('get_binary_from_id') + +def get_binaries_from_name(package, session=None): + """ + Returns list of DBBinary objects for given C{package} name + + @type package: str + @param package: DBBinary package name to search for - def commit(self): - return self.db_con.commit() + @type session: Session + @param session: Optional SQL session object (a temporary one will be + generated if not supplied) - ## Get functions - def __get_single_id(self, query, values, cachename=None): - # This is a bit of a hack but it's an internal function only - if cachename is not None: - res = self.caches[cachename].GetValue(values) - if res: - return res + @rtype: list + @return: list of DBBinary objects for the given name (may be empty) + """ + if session is None: + session = DBConn().session() + return session.query(DBBinary).filter_by(package=package).all() + +__all__.append('get_binaries_from_name') + +def get_binary_from_name_suite(package, suitename, session=None): + ### For dak examine-package + ### XXX: Doesn't use object API yet + if session is None: + session = DBConn().session() + + sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name + FROM binaries b, files fi, location l, component c, bin_associations ba, suite su + WHERE b.package=:package + AND b.file = fi.id + AND fi.location = l.id + AND l.component = c.id + AND ba.bin=b.id + AND ba.suite = su.id + AND su.suite_name=:suitename + ORDER BY b.version DESC""" + + return session.execute(sql, {'package': package, 'suitename': suitename}) + +__all__.append('get_binary_from_name_suite') + +def get_binary_components(package, suitename, arch, session=None): +# Check for packages that have moved from one component to another + query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f + WHERE b.package=:package AND s.suite_name=:suitename + AND (a.arch_string = :arch OR a.arch_string = 'all') + AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id + AND f.location = l.id + AND l.component = c.id + AND b.file = f.id""" + + vals = {'package': package, 'suitename': suitename, 'arch': arch} + + if session is None: + session = DBConn().session() + return session.execute(query, vals) + +__all__.append('get_binary_components') +################################################################################ - c = self.db_con.cursor() - c.execute(query, values) +class Component(object): + def __init__(self, *args, **kwargs): + pass - if c.rowcount != 1: - return None + def __repr__(self): + return '' % self.component_name - res = c.fetchone()[0] - if cachename is not None: - self.caches[cachename].SetValue(values, res) +__all__.append('Component') + +def get_component(component, session=None): + """ + Returns database id for given C{component}. - return res + @type component: string + @param component: The name of the override type - def __get_id(self, retfield, table, qfield, value): - query = "SELECT %s FROM %s WHERE %s = %%(value)s" % (retfield, table, qfield) - return self.__get_single_id(query, {'value': value}, cachename=table) + @rtype: int + @return: the database id for the given component - def get_suite_id(self, suite): - """ - Returns database id for given C{suite}. - Results are kept in a cache during runtime to minimize database queries. - - @type suite: string - @param suite: The name of the suite + """ + component = component.lower() + if session is None: + session = DBConn().session() + q = session.query(Component).filter_by(component_name=component) + if q.count() == 0: + return None + return q.one() - @rtype: int - @return: the database id for the given suite +__all__.append('get_component') - """ - return int(self.__get_id('id', 'suite', 'suite_name', suite)) +################################################################################ - def get_section_id(self, section): - """ - Returns database id for given C{section}. - Results are kept in a cache during runtime to minimize database queries. +class DBConfig(object): + def __init__(self, *args, **kwargs): + pass - @type section: string - @param section: The name of the section + def __repr__(self): + return '' % self.name - @rtype: int - @return: the database id for the given section +__all__.append('DBConfig') - """ - return self.__get_id('id', 'section', 'section', section) +################################################################################ - def get_priority_id(self, priority): - """ - Returns database id for given C{priority}. - Results are kept in a cache during runtime to minimize database queries. +class ContentFilename(object): + def __init__(self, *args, **kwargs): + pass - @type priority: string - @param priority: The name of the priority + def __repr__(self): + return '' % self.filename - @rtype: int - @return: the database id for the given priority +__all__.append('ContentFilename') - """ - return self.__get_id('id', 'priority', 'priority', priority) +def get_or_set_contents_file_id(filename, session=None): + """ + Returns database id for given filename. - def get_override_type_id(self, override_type): - """ - Returns database id for given override C{type}. - Results are kept in a cache during runtime to minimize database queries. + If no matching file is found, a row is inserted. - @type type: string - @param type: The name of the override type + @type filename: string + @param filename: The filename + @type session: SQLAlchemy + @param session: Optional SQL session object (a temporary one will be + generated if not supplied). If not passed, a commit will be performed at + the end of the function, otherwise the caller is responsible for commiting. - @rtype: int - @return: the database id for the given override type + @rtype: int + @return: the database id for the given component + """ + privatetrans = False + if session is None: + session = DBConn().session() + privatetrans = True + + try: + q = session.query(ContentFilename).filter_by(filename=filename) + if q.count() < 1: + cf = ContentFilename() + cf.filename = filename + session.add(cf) + if privatetrans: + session.commit() + return cf.cafilename_id + else: + return q.one().cafilename_id - """ - return self.__get_id('id', 'override_type', 'type', override_type) + except: + traceback.print_exc() + raise - def get_architecture_id(self, architecture): - """ - Returns database id for given C{architecture}. - Results are kept in a cache during runtime to minimize database queries. +__all__.append('get_or_set_contents_file_id') - @type architecture: string - @param architecture: The name of the override type +def get_contents(suite, overridetype, section=None, session=None): + """ + Returns contents for a suite / overridetype combination, limiting + to a section if not None. - @rtype: int - @return: the database id for the given architecture + @type suite: Suite + @param suite: Suite object - """ - return self.__get_id('id', 'architecture', 'arch_string', architecture) + @type overridetype: OverrideType + @param overridetype: OverrideType object - def get_archive_id(self, archive): - """ - returns database id for given c{archive}. - results are kept in a cache during runtime to minimize database queries. + @type section: Section + @param section: Optional section object to limit results to - @type archive: string - @param archive: the name of the override type + @type session: SQLAlchemy + @param session: Optional SQL session object (a temporary one will be + generated if not supplied) - @rtype: int - @return: the database id for the given archive + @rtype: ResultsProxy + @return: ResultsProxy object set up to return tuples of (filename, section, + package, arch_id) + """ - """ - return self.__get_id('id', 'archive', 'lower(name)', archive) + if session is None: + session = DBConn().session() - def get_component_id(self, component): - """ - Returns database id for given C{component}. - Results are kept in a cache during runtime to minimize database queries. + # find me all of the contents for a given suite + contents_q = """SELECT (p.path||'/'||n.file) AS fn, + s.section, + b.package, + b.architecture + FROM content_associations c join content_file_paths p ON (c.filepath=p.id) + JOIN content_file_names n ON (c.filename=n.id) + JOIN binaries b ON (b.id=c.binary_pkg) + JOIN override o ON (o.package=b.package) + JOIN section s ON (s.id=o.section) + WHERE o.suite = :suiteid AND o.type = :overridetypeid + AND b.type=:overridetypename""" - @type component: string - @param component: The name of the override type + vals = {'suiteid': suite.suite_id, + 'overridetypeid': overridetype.overridetype_id, + 'overridetypename': overridetype.overridetype} - @rtype: int - @return: the database id for the given component + if section is not None: + contents_q += " AND s.id = :sectionid" + vals['sectionid'] = section.section_id - """ - return self.__get_id('id', 'component', 'lower(name)', component) + contents_q += " ORDER BY fn" - def get_location_id(self, location, component, archive): - """ - Returns database id for the location behind the given combination of - - B{location} - the path of the location, eg. I{/srv/ftp.debian.org/ftp/pool/} - - B{component} - the id of the component as returned by L{get_component_id} - - B{archive} - the id of the archive as returned by L{get_archive_id} - Results are kept in a cache during runtime to minimize database queries. + return session.execute(contents_q, vals) - @type location: string - @param location: the path of the location +__all__.append('get_contents') - @type component: int - @param component: the id of the component +################################################################################ - @type archive: int - @param archive: the id of the archive +class ContentFilepath(object): + def __init__(self, *args, **kwargs): + pass - @rtype: int - @return: the database id for the location + def __repr__(self): + return '' % self.filepath - """ +__all__.append('ContentFilepath') - archive_id = self.get_archive_id(archive) +def get_or_set_contents_path_id(filepath, session): + """ + Returns database id for given path. - if not archive_id: - return None + If no matching file is found, a row is inserted. - res = None + @type filename: string + @param filename: The filepath + @type session: SQLAlchemy + @param session: Optional SQL session object (a temporary one will be + generated if not supplied). If not passed, a commit will be performed at + the end of the function, otherwise the caller is responsible for commiting. - if component: - component_id = self.get_component_id(component) - if component_id: - res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND component=%(component)s AND archive=%(archive)s", - {'location': location, - 'archive': int(archive_id), - 'component': component_id}, cachename='location') + @rtype: int + @return: the database id for the given path + """ + privatetrans = False + if session is None: + session = DBConn().session() + privatetrans = True + + try: + q = session.query(ContentFilepath).filter_by(filepath=filepath) + if q.count() < 1: + cf = ContentFilepath() + cf.filepath = filepath + session.add(cf) + if privatetrans: + session.commit() + return cf.cafilepath_id else: - res = self.__get_single_id("SELECT id FROM location WHERE path=%(location)s AND archive=%(archive)d", - {'location': location, 'archive': archive_id, 'component': ''}, cachename='location') + return q.one().cafilepath_id - return res + except: + traceback.print_exc() + raise - def get_source_id(self, source, version): - """ - Returns database id for the combination of C{source} and C{version} - - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc} - - B{version} - Results are kept in a cache during runtime to minimize database queries. +__all__.append('get_or_set_contents_path_id') - @type source: string - @param source: source package name +################################################################################ - @type version: string - @param version: the source version +class ContentAssociation(object): + def __init__(self, *args, **kwargs): + pass - @rtype: int - @return: the database id for the source + def __repr__(self): + return '' % self.ca_id - """ - return self.__get_single_id("SELECT id FROM source s WHERE s.source=%(source)s AND s.version=%(version)s", - {'source': source, 'version': version}, cachename='source') +__all__.append('ContentAssociation') - def get_suite_version(self, source, suite): - """ - Returns database id for a combination of C{source} and C{suite}. +def insert_content_paths(binary_id, fullpaths, session=None): + """ + Make sure given path is associated with given binary id + + @type binary_id: int + @param binary_id: the id of the binary + @type fullpaths: list + @param fullpaths: the list of paths of the file being associated with the binary + @type session: SQLAlchemy session + @param session: Optional SQLAlchemy session. If this is passed, the caller + is responsible for ensuring a transaction has begun and committing the + results or rolling back based on the result code. If not passed, a commit + will be performed at the end of the function, otherwise the caller is + responsible for commiting. + + @return: True upon success + """ - - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc} - - B{suite} - a suite name, eg. I{unstable} + privatetrans = False - Results are kept in a cache during runtime to minimize database queries. + if session is None: + session = DBConn().session() + privatetrans = True - @type source: string - @param source: source package name + try: + for fullpath in fullpaths: + (path, file) = os.path.split(fullpath) - @type suite: string - @param suite: the suite name + # Get the necessary IDs ... + ca = ContentAssociation() + ca.binary_id = binary_id + ca.filename_id = get_or_set_contents_file_id(file) + ca.filepath_id = get_or_set_contents_path_id(path) + session.add(ca) - @rtype: string - @return: the version for I{source} in I{suite} + # Only commit if we set up the session ourself + if privatetrans: + session.commit() - """ - return self.__get_single_id(""" - SELECT s.version FROM source s, suite su, src_associations sa - WHERE sa.source=s.id - AND sa.suite=su.id - AND su.suite_name=%(suite)s - AND s.source=%(source)""", {'suite': suite, 'source': source}, cachename='suite_version') + return True + except: + traceback.print_exc() + # Only rollback if we set up the session ourself + if privatetrans: + session.rollback() - def get_files_id (self, filename, size, md5sum, location_id): - """ - Returns -1, -2 or the file_id for filename, if its C{size} and C{md5sum} match an - existing copy. + return False - The database is queried using the C{filename} and C{location_id}. If a file does exist - at that location, the existing size and md5sum are checked against the provided - parameters. A size or checksum mismatch returns -2. If more than one entry is - found within the database, a -1 is returned, no result returns None, otherwise - the file id. +__all__.append('insert_content_paths') - Results are kept in a cache during runtime to minimize database queries. +################################################################################ - @type filename: string - @param filename: the filename of the file to check against the DB +class DSCFile(object): + def __init__(self, *args, **kwargs): + pass - @type size: int - @param size: the size of the file to check against the DB + def __repr__(self): + return '' % self.dscfile_id - @type md5sum: string - @param md5sum: the md5sum of the file to check against the DB +__all__.append('DSCFile') - @type location_id: int - @param location_id: the id of the location as returned by L{get_location_id} +################################################################################ + +class PoolFile(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % self.filename + +__all__.append('PoolFile') + +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 + """ + + if session is not None: + session = DBConn().session() + + 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') + +################################################################################ + +class Fingerprint(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % self.fingerprint + +__all__.append('Fingerprint') + +################################################################################ + +class Keyring(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % self.keyring_name + +__all__.append('Keyring') + +################################################################################ + +class Location(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % (self.path, self.location_id) + +__all__.append('Location') + +def get_location(location, component=None, archive=None, session=None): + """ + Returns Location object for the given combination of location, component + and archive + + @type location: string + @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/} + + @type component: string + @param component: the component name (if None, no restriction applied) + + @type archive: string + @param archive_id: the archive name (if None, no restriction applied) + + @rtype: Location / None + @return: Either a Location object or None if one can't be found + """ + + if session is None: + session = DBConn().session() + + q = session.query(Location).filter_by(path=location) + + if archive is not None: + q = q.join(Archive).filter_by(archive_name=archive) + + if component is not None: + q = q.join(Component).filter_by(component_name=component) + + if q.count() < 1: + return None + else: + return q.one() + +__all__.append('get_location') + +################################################################################ + +class Maintainer(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '''''' % (self.name, self.maintainer_id) + + def get_split_maintainer(self): + if not hasattr(self, 'name') or self.name is None: + return ('', '', '', '') + + return fix_maintainer(self.name.strip()) + +__all__.append('Maintainer') + +################################################################################ + +class Override(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % (self.package, self.suite_id) - @rtype: int / None - @return: Various return values are possible: - - -2: size/checksum error - - -1: more than one file found in database - - None: no file found in database - - int: file id +__all__.append('Override') - """ - values = {'filename' : filename, - 'location' : location_id} +################################################################################ + +class OverrideType(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % self.overridetype + +__all__.append('OverrideType') + +def get_override_type(override_type, session=None): + """ + Returns OverrideType object for given C{override type}. + + @type override_type: string + @param override_type: The name of the override type + + @type session: Session + @param session: Optional SQLA session object (a temporary one will be + generated if not supplied) + + @rtype: int + @return: the database id for the given override type - res = self.caches['files'].GetValue( values ) + """ + if session is None: + session = DBConn().session() + q = session.query(OverrideType).filter_by(overridetype=override_type) + if q.count() == 0: + return None + return q.one() + +__all__.append('get_override_type') + +################################################################################ + +class PendingContentAssociation(object): + def __init__(self, *args, **kwargs): + pass - if not res: - query = """SELECT id, size, md5sum - FROM files - WHERE filename = %(filename)s AND location = %(location)s""" + def __repr__(self): + return '' % self.pca_id - cursor = self.db_con.cursor() - cursor.execute( query, values ) +__all__.append('PendingContentAssociation') - if cursor.rowcount == 0: - res = None +def insert_pending_content_paths(package, fullpaths, session=None): + """ + Make sure given paths are temporarily associated with given + package + + @type package: dict + @param package: the package to associate with should have been read in from the binary control file + @type fullpaths: list + @param fullpaths: the list of paths of the file being associated with the binary + @type session: SQLAlchemy session + @param session: Optional SQLAlchemy session. If this is passed, the caller + is responsible for ensuring a transaction has begun and committing the + results or rolling back based on the result code. If not passed, a commit + will be performed at the end of the function + + @return: True upon success, False if there is a problem + """ - elif cursor.rowcount != 1: - res = -1 + privatetrans = False - else: - row = cursor.fetchone() + if session is None: + session = DBConn().session() + privatetrans = True - if row[1] != size or row[2] != md5sum: - res = -2 + try: + arch = get_architecture(package['Architecture'], session) + arch_id = arch.arch_id - else: - self.caches[cachename].SetValue(values, row[0]) - res = row[0] + # Remove any already existing recorded files for this package + q = session.query(PendingContentAssociation) + q = q.filter_by(package=package['Package']) + q = q.filter_by(version=package['Version']) + q = q.filter_by(architecture=arch_id) + q.delete() - return res + # Insert paths + for fullpath in fullpaths: + (path, file) = os.path.split(fullpath) + if path.startswith( "./" ): + path = path[2:] - def get_or_set_contents_file_id(self, filename): - """ - Returns database id for given filename. + pca = PendingContentAssociation() + pca.package = package['Package'] + pca.version = package['Version'] + pca.filename_id = get_or_set_contents_file_id(file, session) + pca.filepath_id = get_or_set_contents_path_id(path, session) + pca.architecture = arch_id + session.add(pca) - Results are kept in a cache during runtime to minimize database queries. - If no matching file is found, a row is inserted. + # Only commit if we set up the session ourself + if privatetrans: + session.commit() - @type filename: string - @param filename: The filename + return True + except: + traceback.print_exc() - @rtype: int - @return: the database id for the given component - """ - try: - values={'value': filename} - query = "SELECT id FROM content_file_names WHERE file = %(value)s" - id = self.__get_single_id(query, values, cachename='content_file_names') - if not id: - c = self.db_con.cursor() - c.execute( "INSERT INTO content_file_names VALUES (DEFAULT, %(value)s) RETURNING id", - values ) + # Only rollback if we set up the session ourself + if privatetrans: + session.rollback() - id = c.fetchone()[0] - self.caches['content_file_names'].SetValue(values, id) - - return id - except: - traceback.print_exc() - raise + return False - def get_or_set_contents_path_id(self, path): - """ - Returns database id for given path. +__all__.append('insert_pending_content_paths') - Results are kept in a cache during runtime to minimize database queries. - If no matching file is found, a row is inserted. - - @type path: string - @param path: The filename +################################################################################ - @rtype: int - @return: the database id for the given component - """ - try: - values={'value': path} - query = "SELECT id FROM content_file_paths WHERE path = %(value)s" - id = self.__get_single_id(query, values, cachename='content_path_names') - if not id: - c = self.db_con.cursor() - c.execute( "INSERT INTO content_file_paths VALUES (DEFAULT, %(value)s) RETURNING id", - values ) - - id = c.fetchone()[0] - self.caches['content_path_names'].SetValue(values, id) - - return id - except: - traceback.print_exc() - raise - - def get_suite_architectures(self, suite): - """ - Returns list of architectures for C{suite}. - - @type suite: string, int - @param suite: the suite name or the suite_id +class Priority(object): + def __init__(self, *args, **kwargs): + pass - @rtype: list - @return: the list of architectures for I{suite} - """ + def __repr__(self): + return '' % (self.priority, self.priority_id) - suite_id = None - if type(suite) == str: - suite_id = self.get_suite_id(suite) - elif type(suite) == int: - suite_id = suite +__all__.append('Priority') + +def get_priority(priority, session=None): + """ + Returns Priority object for given C{priority name}. + + @type priority: string + @param priority: The name of the priority + + @type session: Session + @param session: Optional SQLA session object (a temporary one will be + generated if not supplied) + + @rtype: Priority + @return: Priority object for the given priority + + """ + if session is None: + session = DBConn().session() + q = session.query(Priority).filter_by(priority=priority) + if q.count() == 0: + return None + return q.one() + +__all__.append('get_priority') + +################################################################################ + +class Queue(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % self.queue_name + +__all__.append('Queue') + +################################################################################ + +class QueueBuild(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % (self.filename, self.queue_id) + +__all__.append('QueueBuild') + +################################################################################ + +class Section(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '
' % self.section + +__all__.append('Section') + +def get_section(section, session=None): + """ + Returns Section object for given C{section name}. + + @type section: string + @param section: The name of the section + + @type session: Session + @param session: Optional SQLA session object (a temporary one will be + generated if not supplied) + + @rtype: Section + @return: Section object for the given section name + + """ + if session is None: + session = DBConn().session() + q = session.query(Section).filter_by(section=section) + if q.count() == 0: + return None + return q.one() + +__all__.append('get_section') + +################################################################################ + +class DBSource(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % (self.source, self.version) + +__all__.append('DBSource') + +def get_sources_from_name(source, dm_upload_allowed=None, session=None): + """ + Returns list of DBSource objects for given C{source} name + + @type source: str + @param source: DBSource package name to search for + + @type dm_upload_allowed: bool + @param dm_upload_allowed: If None, no effect. If True or False, only + return packages with that dm_upload_allowed setting + + @type session: Session + @param session: Optional SQL session object (a temporary one will be + generated if not supplied) + + @rtype: list + @return: list of DBSource objects for the given name (may be empty) + """ + if session is None: + session = DBConn().session() + + q = session.query(DBSource).filter_by(source=source) + if dm_upload_allowed is not None: + q = q.filter_by(dm_upload_allowed=dm_upload_allowed) + + return q.all() + +__all__.append('get_sources_from_name') + +def get_source_in_suite(source, suite, session=None): + """ + Returns list of DBSource objects 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} + + @type source: string + @param source: source package name + + @type suite: string + @param suite: the suite name + + @rtype: string + @return: the version for I{source} in I{suite} + + """ + if session is None: + session = DBConn().session() + q = session.query(SrcAssociation) + q = q.join('source').filter_by(source=source) + q = q.join('suite').filter_by(suite_name=suite) + if q.count() == 0: + return None + # ???: Maybe we should just return the SrcAssociation object instead + return q.one().source + +__all__.append('get_source_in_suite') + +################################################################################ + +class SrcAssociation(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % (self.sa_id, self.source, self.suite) + +__all__.append('SrcAssociation') + +################################################################################ + +class SrcUploader(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % self.uploader_id + +__all__.append('SrcUploader') + +################################################################################ + +class Suite(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % self.suite_name + +__all__.append('Suite') + +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 suite: str + @param suite: Suite name to search for + + @type architecture: str + @param architecture: Architecture name to search for + + @type session: Session + @param session: Optional SQL session object (a temporary one will be + generated if not supplied) + + @rtype: SuiteArchitecture + @return: the SuiteArchitecture object or None + """ + + if session is None: + session = DBConn().session() + + q = session.query(SuiteArchitecture) + q = q.join(Architecture).filter_by(arch_string=architecture) + q = q.join(Suite).filter_by(suite_name=suite) + if q.count() == 0: + return None + return q.one() + +__all__.append('get_suite_architecture') + +def get_suite(suite, session=None): + """ + Returns Suite object for given C{suite name}. + + @type suite: string + @param suite: The name of the suite + + @type session: Session + @param session: Optional SQLA session object (a temporary one will be + generated if not supplied) + + @rtype: Suite + @return: Suite object for the requested suite name (None if not presenT) + + """ + if session is None: + session = DBConn().session() + q = session.query(Suite).filter_by(suite_name=suite) + if q.count() == 0: + return None + return q.one() + +__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') + +def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None): + """ + Returns list of Architecture objects for given C{suite} name + + @type source: str + @param source: Suite name to search for + + @type skipsrc: boolean + @param skipsrc: Whether to skip returning the 'source' architecture entry + (Default False) + + @type skipall: boolean + @param skipall: Whether to skip returning the 'all' architecture entry + (Default False) + + @type session: Session + @param session: Optional SQL session object (a temporary one will be + generated if not supplied) + + @rtype: list + @return: list of Architecture objects for the given name (may be empty) + """ + + if session is None: + session = DBConn().session() + + 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() + +__all__.append('get_suite_architectures') + +################################################################################ + +class Uid(object): + def __init__(self, *args, **kwargs): + pass + + def __repr__(self): + return '' % (self.uid, self.name) + +__all__.append('Uid') + +def add_database_user(uidname, session=None): + """ + Adds a database user + + @type uidname: string + @param uidname: The uid of the user to add + + @type session: SQLAlchemy + @param session: Optional SQL session object (a temporary one will be + generated if not supplied). If not passed, a commit will be performed at + the end of the function, otherwise the caller is responsible for commiting. + + @rtype: Uid + @return: the uid object for the given uidname + """ + privatetrans = False + if session is None: + session = DBConn().session() + privatetrans = True + + try: + session.execute("CREATE USER :uid", {'uid': uidname}) + if privatetrans: + session.commit() + except: + traceback.print_exc() + raise + +__all__.append('add_database_user') + +def get_or_set_uid(uidname, session=None): + """ + Returns uid object for given uidname. + + If no matching uidname is found, a row is inserted. + + @type uidname: string + @param uidname: The uid to add + + @type session: SQLAlchemy + @param session: Optional SQL session object (a temporary one will be + generated if not supplied). If not passed, a commit will be performed at + the end of the function, otherwise the caller is responsible for commiting. + + @rtype: Uid + @return: the uid object for the given uidname + """ + privatetrans = False + if session is None: + session = DBConn().session() + privatetrans = True + + try: + q = session.query(Uid).filter_by(uid=uidname) + if q.count() < 1: + uid = Uid() + uid.uid = uidname + session.add(uid) + if privatetrans: + session.commit() + return uid + else: + return q.one() + + except: + traceback.print_exc() + raise + +__all__.append('get_or_set_uid') + + +def get_uid_from_fingerprint(fpr, session=None): + if session is None: + session = DBConn().session() + + q = session.query(Uid) + q = q.join(Fingerprint).filter_by(fingerprint=fpr) + + if q.count() != 1: + return None + else: + return q.one() + +__all__.append('get_uid_from_fingerprint') + +################################################################################ + +class DBConn(Singleton): + """ + database module init. + """ + def __init__(self, *args, **kwargs): + super(DBConn, self).__init__(*args, **kwargs) + + def _startup(self, *args, **kwargs): + self.debug = False + if kwargs.has_key('debug'): + self.debug = True + self.__createconn() + + def __setuptables(self): + self.tbl_architecture = Table('architecture', self.db_meta, autoload=True) + self.tbl_archive = Table('archive', self.db_meta, autoload=True) + self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True) + self.tbl_binaries = Table('binaries', self.db_meta, autoload=True) + self.tbl_component = Table('component', self.db_meta, autoload=True) + self.tbl_config = Table('config', self.db_meta, autoload=True) + self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True) + self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True) + self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True) + self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True) + self.tbl_files = Table('files', self.db_meta, autoload=True) + self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True) + self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True) + self.tbl_location = Table('location', self.db_meta, autoload=True) + self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True) + self.tbl_override = Table('override', self.db_meta, autoload=True) + self.tbl_override_type = Table('override_type', self.db_meta, autoload=True) + self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True) + self.tbl_priority = Table('priority', self.db_meta, autoload=True) + self.tbl_queue = Table('queue', self.db_meta, autoload=True) + self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True) + self.tbl_section = Table('section', self.db_meta, autoload=True) + self.tbl_source = Table('source', self.db_meta, autoload=True) + self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True) + self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True) + self.tbl_suite = Table('suite', self.db_meta, autoload=True) + self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True) + self.tbl_uid = Table('uid', self.db_meta, autoload=True) + + def __setupmappers(self): + mapper(Architecture, self.tbl_architecture, + properties = dict(arch_id = self.tbl_architecture.c.id)) + + 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(DBBinary, self.tbl_binaries, + properties = dict(binary_id = self.tbl_binaries.c.id, + package = self.tbl_binaries.c.package, + version = self.tbl_binaries.c.version, + maintainer_id = self.tbl_binaries.c.maintainer, + maintainer = relation(Maintainer), + source_id = self.tbl_binaries.c.source, + source = relation(DBSource), + arch_id = self.tbl_binaries.c.architecture, + architecture = relation(Architecture), + poolfile_id = self.tbl_binaries.c.file, + poolfile = relation(PoolFile), + 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)))) + + mapper(Component, self.tbl_component, + properties = dict(component_id = self.tbl_component.c.id, + component_name = self.tbl_component.c.name)) + + mapper(DBConfig, self.tbl_config, + properties = dict(config_id = self.tbl_config.c.id)) + + mapper(ContentAssociation, self.tbl_content_associations, + properties = dict(ca_id = self.tbl_content_associations.c.id, + filename_id = self.tbl_content_associations.c.filename, + filename = relation(ContentFilename), + filepath_id = self.tbl_content_associations.c.filepath, + filepath = relation(ContentFilepath), + binary_id = self.tbl_content_associations.c.binary_pkg, + binary = relation(DBBinary))) + + + mapper(ContentFilename, self.tbl_content_file_names, + properties = dict(cafilename_id = self.tbl_content_file_names.c.id, + filename = self.tbl_content_file_names.c.file)) + + mapper(ContentFilepath, self.tbl_content_file_paths, + properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id, + filepath = self.tbl_content_file_paths.c.path)) + + mapper(DSCFile, self.tbl_dsc_files, + properties = dict(dscfile_id = self.tbl_dsc_files.c.id, + source_id = self.tbl_dsc_files.c.source, + source = relation(DBSource), + poolfile_id = self.tbl_dsc_files.c.file, + poolfile = relation(PoolFile))) + + mapper(PoolFile, self.tbl_files, + 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))) + + mapper(Fingerprint, self.tbl_fingerprint, + properties = dict(fingerprint_id = self.tbl_fingerprint.c.id, + uid_id = self.tbl_fingerprint.c.uid, + uid = relation(Uid), + keyring_id = self.tbl_fingerprint.c.keyring, + keyring = relation(Keyring))) + + mapper(Keyring, self.tbl_keyrings, + properties = dict(keyring_name = self.tbl_keyrings.c.name, + keyring_id = self.tbl_keyrings.c.id)) + + mapper(Location, self.tbl_location, + properties = dict(location_id = self.tbl_location.c.id, + component_id = self.tbl_location.c.component, + component = relation(Component), + archive_id = self.tbl_location.c.archive, + archive = relation(Archive), + archive_type = self.tbl_location.c.type)) + + mapper(Maintainer, self.tbl_maintainer, + properties = dict(maintainer_id = self.tbl_maintainer.c.id)) + + mapper(Override, self.tbl_override, + properties = dict(suite_id = self.tbl_override.c.suite, + suite = relation(Suite), + component_id = self.tbl_override.c.component, + component = relation(Component), + priority_id = self.tbl_override.c.priority, + priority = relation(Priority), + section_id = self.tbl_override.c.section, + section = relation(Section), + overridetype_id = self.tbl_override.c.type, + overridetype = relation(OverrideType))) + + mapper(OverrideType, self.tbl_override_type, + properties = dict(overridetype = self.tbl_override_type.c.type, + overridetype_id = self.tbl_override_type.c.id)) + + mapper(PendingContentAssociation, self.tbl_pending_content_associations, + properties = dict(pca_id = self.tbl_pending_content_associations.c.id, + filepath_id = self.tbl_pending_content_associations.c.filepath, + filepath = relation(ContentFilepath), + filename_id = self.tbl_pending_content_associations.c.filename, + filename = relation(ContentFilename))) + + mapper(Priority, self.tbl_priority, + properties = dict(priority_id = self.tbl_priority.c.id)) + + mapper(Queue, self.tbl_queue, + properties = dict(queue_id = self.tbl_queue.c.id)) + + mapper(QueueBuild, self.tbl_queue_build, + properties = dict(suite_id = self.tbl_queue_build.c.suite, + queue_id = self.tbl_queue_build.c.queue, + queue = relation(Queue))) + + mapper(Section, self.tbl_section, + properties = dict(section_id = self.tbl_section.c.id)) + + mapper(DBSource, self.tbl_source, + 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), + 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)))) + + 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(SrcUploader, self.tbl_src_uploaders, + properties = dict(uploader_id = self.tbl_src_uploaders.c.id, + source_id = self.tbl_src_uploaders.c.source, + source = relation(DBSource, + primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)), + maintainer_id = self.tbl_src_uploaders.c.maintainer, + maintainer = relation(Maintainer, + primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id)))) + + mapper(Suite, self.tbl_suite, + properties = dict(suite_id = self.tbl_suite.c.id)) + + 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))) + + mapper(Uid, self.tbl_uid, + properties = dict(uid_id = self.tbl_uid.c.id, + fingerprint = relation(Fingerprint))) + + ## Connection functions + def __createconn(self): + from config import Config + cnf = Config() + if cnf["DB::Host"]: + # TCP/IP + connstr = "postgres://%s" % cnf["DB::Host"] + if cnf["DB::Port"] and cnf["DB::Port"] != "-1": + connstr += ":%s" % cnf["DB::Port"] + connstr += "/%s" % cnf["DB::Name"] else: - return None - - c = self.db_con.cursor() - c.execute( """SELECT a.arch_string FROM suite_architectures sa - JOIN architecture a ON (a.id = sa.architecture) - WHERE suite='%s'""" % suite_id ) - - return map(lambda x: x[0], c.fetchall()) - - def insert_content_paths(self, bin_id, fullpaths): - """ - Make sure given path is associated with given binary id - - @type bin_id: int - @param bin_id: the id of the binary - @type fullpath: string - @param fullpath: the path of the file being associated with the binary - - @return True upon success - """ - - c = self.db_con.cursor() - - c.execute("BEGIN WORK") - try: - - for fullpath in fullpaths: - (path, file) = os.path.split(fullpath) - - # Get the necessary IDs ... - file_id = self.get_or_set_contents_file_id(file) - path_id = self.get_or_set_contents_path_id(path) - - c.execute("""INSERT INTO content_associations - (binary_pkg, filepath, filename) - VALUES ( '%d', '%d', '%d')""" % (bin_id, path_id, file_id) ) - - c.execute("COMMIT") - return True - except: - traceback.print_exc() - c.execute("ROLLBACK") - return False - - def insert_pending_content_paths(self, package, fullpaths): - """ - Make sure given paths are temporarily associated with given - package - - @type package: dict - @param package: the package to associate with should have been read in from the binary control file - @type fullpaths: list - @param fullpaths: the list of paths of the file being associated with the binary - - @return True upon success - """ - - c = self.db_con.cursor() - - c.execute("BEGIN WORK") - try: - arch_id = self.get_architecture_id(package['Architecture']) - - # Remove any already existing recorded files for this package - c.execute("""DELETE FROM pending_content_associations - WHERE package=%(Package)s - AND version=%(Version)s - AND arch_id=%d""" % arch_id, package ) - - for fullpath in fullpaths: - (path, file) = os.path.split(fullpath) - - if path.startswith( "./" ): - path = path[2:] - # Get the necessary IDs ... - file_id = self.get_or_set_contents_file_id(file) - path_id = self.get_or_set_contents_path_id(path) - - c.execute("""INSERT INTO pending_content_associations - (package, version, architecture, filepath, filename) - VALUES (%%(Package)s, %%(Version)s, '%d', '%d', '%d')""" - % (arch_id, path_id, file_id), package ) - - c.execute("COMMIT") - return True - except: - traceback.print_exc() - c.execute("ROLLBACK") - return False + # Unix Socket + connstr = "postgres:///%s" % cnf["DB::Name"] + if cnf["DB::Port"] and cnf["DB::Port"] != "-1": + connstr += "?port=%s" % cnf["DB::Port"] + + self.db_pg = create_engine(connstr, echo=self.debug) + self.db_meta = MetaData() + self.db_meta.bind = self.db_pg + self.db_smaker = sessionmaker(bind=self.db_pg, + autoflush=True, + autocommit=False) + + self.__setuptables() + self.__setupmappers() + + def session(self): + return self.db_smaker() + +__all__.append('DBConn') +