X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=daklib%2Fdbconn.py;h=67852949fe35083dededb021e20418292315fa57;hb=2f30eb272c2dfbdb3544d7e0a0e120a9e4be3294;hp=1fca9839d73baf31787904a6f27cb0415ce51936;hpb=f698f038afdccb07487c104dac7c83d3120b6de3;p=dak.git diff --git a/daklib/dbconn.py b/daklib/dbconn.py index 1fca9839..67852949 100755 --- a/daklib/dbconn.py +++ b/daklib/dbconn.py @@ -53,9 +53,10 @@ from tempfile import mkstemp, mkdtemp from inspect import getargspec import sqlalchemy -from sqlalchemy import create_engine, Table, MetaData, Column, Integer, desc +from sqlalchemy import create_engine, Table, MetaData, Column, Integer, desc, \ + Text, ForeignKey from sqlalchemy.orm import sessionmaker, mapper, relation, object_session, \ - backref, MapperExtension, EXT_CONTINUE + backref, MapperExtension, EXT_CONTINUE, object_mapper from sqlalchemy import types as sqltypes # Don't remove this, we re-export the exceptions to scripts which import us @@ -287,6 +288,50 @@ class ORMObject(object): ''' return session.query(cls).get(primary_key) + def session(self, replace = False): + ''' + Returns the current session that is associated with the object. May + return None is object is in detached state. + ''' + + return object_session(self) + + def clone(self, session = None): + ''' + Clones the current object in a new session and returns the new clone. A + fresh session is created if the optional session parameter is not + provided. The function will fail if a session is provided and has + unflushed changes. + + RATIONALE: SQLAlchemy's session is not thread safe. This method clones + an existing object to allow several threads to work with their own + instances of an ORMObject. + + WARNING: Only persistent (committed) objects can be cloned. Changes + made to the original object that are not committed yet will get lost. + The session of the new object will always be rolled back to avoid + ressource leaks. + ''' + + if self.session() is None: + raise RuntimeError( \ + 'Method clone() failed for detached object:\n%s' % self) + self.session().flush() + mapper = object_mapper(self) + primary_key = mapper.primary_key_from_instance(self) + object_class = self.__class__ + if session is None: + session = DBConn().session() + elif len(session.new) + len(session.dirty) + len(session.deleted) > 0: + raise RuntimeError( \ + 'Method clone() failed due to unflushed changes in session.') + new_object = session.query(object_class).get(primary_key) + session.rollback() + if new_object is None: + raise RuntimeError( \ + 'Method clone() failed for non-persistent object:\n%s' % self) + return new_object + __all__.append('ORMObject') ################################################################################ @@ -421,12 +466,9 @@ __all__.append('get_archive') ################################################################################ -class BinContents(object): - def __init__(self, *args, **kwargs): - pass - - def __repr__(self): - return '' % (self.binary, self.filename) +class BinContents(ORMObject): + def properties(silf): + return ['file', 'binary'] __all__.append('BinContents') @@ -447,7 +489,7 @@ class DBBinary(ORMObject): def properties(self): return ['package', 'version', 'maintainer', 'source', 'architecture', \ 'poolfile', 'binarytype', 'fingerprint', 'install_date', \ - 'suites_count', 'binary_id'] + 'suites_count', 'binary_id', 'contents_count'] def not_null_constraints(self): return ['package', 'version', 'maintainer', 'source', 'poolfile', \ @@ -1161,7 +1203,7 @@ class PoolFile(ORMObject): def fullpath(self): return os.path.join(self.location.path, self.filename) - def is_valid(self, filesize = -1, md5sum = None):\ + def is_valid(self, filesize = -1, md5sum = None): return self.filesize == long(filesize) and self.md5sum == md5sum def properties(self): @@ -1575,8 +1617,6 @@ __all__.append('get_dbchange') ################################################################################ -# 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 @@ -1585,7 +1625,8 @@ class Location(ORMObject): self.archive_type = 'pool' def properties(self): - return ['path', 'archive_type', 'component', 'files_count'] + return ['path', 'location_id', 'archive_type', 'component', \ + 'files_count'] def not_null_constraints(self): return ['path', 'archive_type'] @@ -2861,12 +2902,11 @@ class DBConn(object): # 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', + 'changes', + 'build_queue_files', ) tables_no_primary = ( - 'bin_contents', 'changes_pending_files_map', 'changes_pending_source_files', 'changes_pool_files', @@ -2877,8 +2917,8 @@ class DBConn(object): 'suite_build_queue_copy', 'udeb_contents', # see the comment above - 'changes', - 'build_queue_files', + #'changes', + #'build_queue_files', ) views = ( @@ -2918,6 +2958,14 @@ class DBConn(object): table = Table(table_name, self.db_meta, autoload=True) setattr(self, 'tbl_%s' % table_name, table) + # bin_contents needs special attention until update #41 has been + # applied + self.tbl_bin_contents = Table('bin_contents', self.db_meta, \ + Column('file', Text, primary_key = True), + Column('binary_id', Integer, ForeignKey('binaries.id'), \ + primary_key = True), + autoload=True, useexisting=True) + for view_name in views: view = Table(view_name, self.db_meta, autoload=True) setattr(self, 'view_%s' % view_name, view) @@ -3190,6 +3238,12 @@ class DBConn(object): fingerprint = relation(Fingerprint, backref="uploadblocks"), uid = relation(Uid, backref="uploadblocks"))) + mapper(BinContents, self.tbl_bin_contents, + properties = dict( + binary = relation(DBBinary, + backref=backref('contents', lazy='dynamic')), + file = self.tbl_bin_contents.c.file)) + ## Connection functions def __createconn(self): from config import Config