diff -r de355ee59e52 -r 9c33d98a074e server/schemahooks.py --- a/server/schemahooks.py Sat Aug 01 17:22:05 2009 +0200 +++ b/server/schemahooks.py Sat Aug 01 17:22:46 2009 +0200 @@ -16,14 +16,27 @@ from yams.buildobjs import EntityType, RelationType, RelationDefinition from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints + from cubicweb import ValidationError, RepositoryError -from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES +from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CONSTRAINTS from cubicweb.server import schemaserial as ss from cubicweb.server.sqlutils import SQL_PREFIX from cubicweb.server.pool import Operation, SingleLastOperation, PreCommitOperation from cubicweb.server.hookhelper import (entity_attr, entity_name, check_internal_entity) + +TYPE_CONVERTER = { # XXX + 'Boolean': bool, + 'Int': int, + 'Float': float, + 'Password': str, + 'String': unicode, + 'Date' : unicode, + 'Datetime' : unicode, + 'Time' : unicode, + } + # core entity and relation types which can't be removed CORE_ETYPES = list(BASE_TYPES) + ['CWEType', 'CWRType', 'CWUser', 'CWGroup', 'CWConstraint', 'CWAttribute', 'CWRelation'] @@ -67,45 +80,9 @@ '%s.%s' % (etype, rtype)) -class SchemaOperation(Operation): - """base class for schema operations""" - def __init__(self, session, kobj=None, **kwargs): - self.schema = session.repo.schema - self.kobj = kobj - # once Operation.__init__ has been called, event may be triggered, so - # do this last ! - Operation.__init__(self, session, **kwargs) - # every schema operation is triggering a schema update - UpdateSchemaOp(session) +# operations for low-level database alteration ################################ -class EarlySchemaOperation(SchemaOperation): - def insert_index(self): - """schema operation which are inserted at the begining of the queue - (typically to add/remove entity or relation types) - """ - i = -1 - for i, op in enumerate(self.session.pending_operations): - if not isinstance(op, EarlySchemaOperation): - return i - return i + 1 - -class UpdateSchemaOp(SingleLastOperation): - """the update schema operation: - - special operation which should be called once and after all other schema - operations. It will trigger internal structures rebuilding to consider - schema changes - """ - - def __init__(self, session): - self.repo = session.repo - SingleLastOperation.__init__(self, session) - - def commit_event(self): - self.repo.set_schema(self.repo.schema) - - -class DropTableOp(PreCommitOperation): +class DropTable(PreCommitOperation): """actually remove a database from the instance's schema""" table = None # make pylint happy def precommit_event(self): @@ -117,7 +94,15 @@ self.session.system_sql('DROP TABLE %s' % self.table) self.info('dropped table %s', self.table) -class DropColumnOp(PreCommitOperation): + +class DropRelationTable(DropTable): + def __init__(self, session, rtype): + super(DropRelationTable, self).__init__( + session, table='%s_relation' % rtype) + session.transaction_data.setdefault('pendingrtypes', set()).add(rtype) + + +class DropColumn(PreCommitOperation): """actually remove the attribut's column from entity table in the system database """ @@ -135,230 +120,152 @@ self.error('error while altering table %s: %s', table, ex) -# deletion #################################################################### +# base operations for in-memory schema synchronization ######################## -class DeleteCWETypeOp(SchemaOperation): - """actually remove the entity type from the instance's schema""" - def commit_event(self): - try: - # del_entity_type also removes entity's relations - self.schema.del_entity_type(self.kobj) - except KeyError: - # s/o entity type have already been deleted - pass +class MemSchemaNotifyChanges(SingleLastOperation): + """the update schema operation: -def before_del_eetype(session, eid): - """before deleting a CWEType entity: - * check that we don't remove a core entity type - * cascade to delete related CWAttribute and CWRelation entities - * instantiate an operation to delete the entity type on commit + special operation which should be called once and after all other schema + operations. It will trigger internal structures rebuilding to consider + schema changes """ - # final entities can't be deleted, don't care about that - name = check_internal_entity(session, eid, CORE_ETYPES) - # delete every entities of this type - session.unsafe_execute('DELETE %s X' % name) - DropTableOp(session, table=SQL_PREFIX + name) - DeleteCWETypeOp(session, name) -def after_del_eetype(session, eid): - # workflow cleanup - session.execute('DELETE State X WHERE NOT X state_of Y') - session.execute('DELETE Transition X WHERE NOT X transition_of Y') + def __init__(self, session): + self.repo = session.repo + SingleLastOperation.__init__(self, session) + + def commit_event(self): + self.repo.set_schema(self.repo.schema) -class DeleteCWRTypeOp(SchemaOperation): - """actually remove the relation type from the instance's schema""" - def commit_event(self): - try: - self.schema.del_relation_type(self.kobj) - except KeyError: - # s/o entity type have already been deleted - pass +class MemSchemaOperation(Operation): + """base class for schema operations""" + def __init__(self, session, kobj=None, **kwargs): + self.schema = session.schema + self.kobj = kobj + # once Operation.__init__ has been called, event may be triggered, so + # do this last ! + Operation.__init__(self, session, **kwargs) + # every schema operation is triggering a schema update + MemSchemaNotifyChanges(session) -def before_del_ertype(session, eid): - """before deleting a CWRType entity: - * check that we don't remove a core relation type - * cascade to delete related CWAttribute and CWRelation entities - * instantiate an operation to delete the relation type on commit - """ - name = check_internal_entity(session, eid, CORE_RTYPES) - # delete relation definitions using this relation type - session.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s', - {'x': eid}) - session.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s', - {'x': eid}) - DeleteCWRTypeOp(session, name) + def prepare_constraints(self, subjtype, rtype, objtype): + constraints = rtype.rproperty(subjtype, objtype, 'constraints') + self.constraints = list(constraints) + rtype.set_rproperty(subjtype, objtype, 'constraints', self.constraints) -class DeleteRelationDefOp(SchemaOperation): - """actually remove the relation definition from the instance's schema""" - def commit_event(self): - subjtype, rtype, objtype = self.kobj - try: - self.schema.del_relation_def(subjtype, rtype, objtype) - except KeyError: - # relation type may have been already deleted - pass +class MemSchemaEarlyOperation(MemSchemaOperation): + def insert_index(self): + """schema operation which are inserted at the begining of the queue + (typically to add/remove entity or relation types) + """ + i = -1 + for i, op in enumerate(self.session.pending_operations): + if not isinstance(op, MemSchemaEarlyOperation): + return i + return i + 1 + -def after_del_relation_type(session, rdefeid, rtype, rteid): - """before deleting a CWAttribute or CWRelation entity: - * if this is a final or inlined relation definition, instantiate an - operation to drop necessary column, else if this is the last instance - of a non final relation, instantiate an operation to drop necessary - table - * instantiate an operation to delete the relation definition on commit - * delete the associated relation type when necessary - """ - subjschema, rschema, objschema = session.repo.schema.schema_by_eid(rdefeid) - pendings = session.transaction_data.get('pendingeids', ()) - # first delete existing relation if necessary - if rschema.is_final(): - rdeftype = 'CWAttribute' - else: - rdeftype = 'CWRelation' - if not (subjschema.eid in pendings or objschema.eid in pendings): - session.execute('DELETE X %s Y WHERE X is %s, Y is %s' - % (rschema, subjschema, objschema)) - execute = session.unsafe_execute - rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,' - 'R eid %%(x)s' % rdeftype, {'x': rteid}) - lastrel = rset[0][0] == 0 - # we have to update physical schema systematically for final and inlined - # relations, but only if it's the last instance for this relation type - # for other relations - - if (rschema.is_final() or rschema.inlined): - rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, ' - 'R eid %%(x)s, X from_entity E, E name %%(name)s' - % rdeftype, {'x': rteid, 'name': str(subjschema)}) - if rset[0][0] == 0 and not subjschema.eid in pendings: - DropColumnOp(session, table=SQL_PREFIX + subjschema.type, - column=SQL_PREFIX + rschema.type) - elif lastrel: - DropTableOp(session, table='%s_relation' % rschema.type) - # if this is the last instance, drop associated relation type - if lastrel and not rteid in pendings: - execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x') - DeleteRelationDefOp(session, (subjschema, rschema, objschema)) +class MemSchemaPermissionOperation(MemSchemaOperation): + """base class to synchronize schema permission definitions""" + def __init__(self, session, perm, etype_eid): + self.perm = perm + try: + self.name = entity_name(session, etype_eid) + except IndexError: + self.error('changing permission of a no more existant type #%s', + etype_eid) + else: + Operation.__init__(self, session) -# addition #################################################################### - -class AddCWETypeOp(EarlySchemaOperation): - """actually add the entity type to the instance's schema""" - eid = None # make pylint happy - def commit_event(self): - self.schema.add_entity_type(self.kobj) +# operations for high-level source database alteration ######################## -def before_add_eetype(session, entity): - """before adding a CWEType entity: - * check that we are not using an existing entity type, - """ - name = entity['name'] - schema = session.repo.schema - if name in schema and schema[name].eid is not None: - raise RepositoryError('an entity type %s already exists' % name) +class SourceDbCWETypeRename(PreCommitOperation): + """this operation updates physical storage accordingly""" + oldname = newname = None # make pylint happy -def after_add_eetype(session, entity): - """after adding a CWEType entity: - * create the necessary table - * set creation_date and modification_date by creating the necessary - CWAttribute entities - * add owned_by relation by creating the necessary CWRelation entity - * register an operation to add the entity type to the instance's - schema on commit - """ - if entity.get('final'): - return - schema = session.repo.schema - name = entity['name'] - etype = EntityType(name=name, description=entity.get('description'), - meta=entity.get('meta')) # don't care about final - # fake we add it to the schema now to get a correctly initialized schema - # but remove it before doing anything more dangerous... - schema = session.repo.schema - eschema = schema.add_entity_type(etype) - eschema.set_default_groups() - # generate table sql and rql to add metadata - tablesql = eschema2sql(session.pool.source('system').dbhelper, eschema, - prefix=SQL_PREFIX) - relrqls = [] - for rtype in (META_RTYPES - VIRTUAL_RTYPES): - rschema = schema[rtype] - sampletype = rschema.subjects()[0] - desttype = rschema.objects()[0] - props = rschema.rproperties(sampletype, desttype) - relrqls += list(ss.rdef2rql(rschema, name, desttype, props)) - # now remove it ! - schema.del_entity_type(name) - # create the necessary table - for sql in tablesql.split(';'): - if sql.strip(): - session.system_sql(sql) - # register operation to modify the schema on commit - # this have to be done before adding other relations definitions - # or permission settings - etype.eid = entity.eid - AddCWETypeOp(session, etype) - # add meta relations - for rql, kwargs in relrqls: - session.execute(rql, kwargs) + def precommit_event(self): + # we need sql to operate physical changes on the system database + sqlexec = self.session.system_sql + sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, self.oldname, + SQL_PREFIX, self.newname)) + self.info('renamed table %s to %s', self.oldname, self.newname) + sqlexec('UPDATE entities SET type=%s WHERE type=%s', + (self.newname, self.oldname)) + sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s', + (self.newname, self.oldname)) -class AddCWRTypeOp(EarlySchemaOperation): - """actually add the relation type to the instance's schema""" - eid = None # make pylint happy - def commit_event(self): - rschema = self.schema.add_relation_type(self.kobj) - rschema.set_default_groups() - -def before_add_ertype(session, entity): - """before adding a CWRType entity: - * check that we are not using an existing relation type, - * register an operation to add the relation type to the instance's - schema on commit +class SourceDbCWRTypeUpdate(PreCommitOperation): + """actually update some properties of a relation definition""" + rschema = values = entity = None # make pylint happy - We don't know yeat this point if a table is necessary - """ - name = entity['name'] - if name in session.repo.schema.relations(): - raise RepositoryError('a relation type %s already exists' % name) - -def after_add_ertype(session, entity): - """after a CWRType entity has been added: - * register an operation to add the relation type to the instance's - schema on commit - We don't know yeat this point if a table is necessary - """ - rtype = RelationType(name=entity['name'], - description=entity.get('description'), - meta=entity.get('meta', False), - inlined=entity.get('inlined', False), - symetric=entity.get('symetric', False)) - rtype.eid = entity.eid - AddCWRTypeOp(session, rtype) + def precommit_event(self): + session = self.session + rschema = self.rschema + if rschema.is_final() or not 'inlined' in self.values: + return # nothing to do + inlined = self.values['inlined'] + entity = self.entity + # check in-lining is necessary / possible + if not entity.inlined_changed(inlined): + return # nothing to do + # inlined changed, make necessary physical changes! + sqlexec = self.session.system_sql + rtype = rschema.type + eidcolumn = SQL_PREFIX + 'eid' + if not inlined: + # need to create the relation if it has not been already done by + # another event of the same transaction + if not rschema.type in session.transaction_data.get('createdtables', ()): + tablesql = rschema2sql(rschema) + # create the necessary table + for sql in tablesql.split(';'): + if sql.strip(): + sqlexec(sql) + session.transaction_data.setdefault('createdtables', []).append( + rschema.type) + # copy existant data + column = SQL_PREFIX + rtype + for etype in rschema.subjects(): + table = SQL_PREFIX + str(etype) + sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL' + % (rtype, eidcolumn, column, table, column)) + # drop existant columns + for etype in rschema.subjects(): + DropColumn(session, table=SQL_PREFIX + str(etype), + column=SQL_PREFIX + rtype) + else: + for etype in rschema.subjects(): + try: + add_inline_relation_column(session, str(etype), rtype) + except Exception, ex: + # the column probably already exists. this occurs when the + # entity's type has just been added or if the column has not + # been previously dropped + self.error('error while altering table %s: %s', etype, ex) + # copy existant data. + # XXX don't use, it's not supported by sqlite (at least at when i tried it) + #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to ' + # 'FROM %(rtype)s_relation ' + # 'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from' + # % locals()) + table = SQL_PREFIX + str(etype) + cursor = sqlexec('SELECT eid_from, eid_to FROM %(table)s, ' + '%(rtype)s_relation WHERE %(table)s.%(eidcolumn)s=' + '%(rtype)s_relation.eid_from' % locals()) + args = [{'val': eid_to, 'x': eid} for eid, eid_to in cursor.fetchall()] + if args: + column = SQL_PREFIX + rtype + cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s' + % (table, column, eidcolumn), args) + # drop existant table + DropRelationTable(session, rtype) -class AddErdefOp(EarlySchemaOperation): - """actually add the attribute relation definition to the instance's - schema - """ - def commit_event(self): - self.schema.add_relation_def(self.kobj) - -TYPE_CONVERTER = { - 'Boolean': bool, - 'Int': int, - 'Float': float, - 'Password': str, - 'String': unicode, - 'Date' : unicode, - 'Datetime' : unicode, - 'Time' : unicode, - } - - -class AddCWAttributePreCommitOp(PreCommitOperation): +class SourceDbCWAttributeAdd(PreCommitOperation): """an attribute relation (CWAttribute) has been added: * add the necessary column * set default on this column if any and possible @@ -368,34 +275,44 @@ constraints are handled by specific hooks """ entity = None # make pylint happy - def precommit_event(self): - session = self.session + + def init_rdef(self, **kwargs): entity = self.entity fromentity = entity.stype - session.execute('SET X ordernum Y+1 WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, X ordernum >= %(order)s, NOT X eid %(x)s', - {'x': entity.eid, 'se': fromentity.eid, 'order': entity.ordernum or 0}) + self.session.execute('SET X ordernum Y+1 ' + 'WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, ' + 'X ordernum >= %(order)s, NOT X eid %(x)s', + {'x': entity.eid, 'se': fromentity.eid, + 'order': entity.ordernum or 0}) subj = str(fromentity.name) rtype = entity.rtype.name obj = str(entity.otype.name) - # at this point default is a string or None, but we need a correctly - # typed value + constraints = get_constraints(self.session, entity) + rdef = RelationDefinition(subj, rtype, obj, + description=entity.description, + cardinality=entity.cardinality, + constraints=constraints, + order=entity.ordernum, + eid=entity.eid, + **kwargs) + MemSchemaRDefAdd(self.session, rdef) + return rdef + + def precommit_event(self): + session = self.session + entity = self.entity + # entity.defaultval is a string or None, but we need a correctly typed + # value default = entity.defaultval if default is not None: - default = TYPE_CONVERTER[obj](default) - constraints = get_constraints(session, entity) - rdef = RelationDefinition(subj, rtype, obj, - cardinality=entity.cardinality, - order=entity.ordernum, - description=entity.description, - default=default, - indexed=entity.indexed, - fulltextindexed=entity.fulltextindexed, - internationalizable=entity.internationalizable, - constraints=constraints, - eid=entity.eid) + default = TYPE_CONVERTER[entity.otype.name](default) + rdef = self.init_rdef(default=default, + indexed=entity.indexed, + fulltextindexed=entity.fulltextindexed, + internationalizable=entity.internationalizable) sysource = session.pool.source('system') attrtype = type_from_constraints(sysource.dbhelper, rdef.object, - constraints) + rdef.constraints) # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to # add a new column with UNIQUE, it should be added after the ALTER TABLE # using ADD INDEX @@ -406,8 +323,8 @@ extra_unique_index = False # added some str() wrapping query since some backend (eg psycopg) don't # allow unicode queries - table = SQL_PREFIX + subj - column = SQL_PREFIX + rtype + table = SQL_PREFIX + rdef.subject + column = SQL_PREFIX + rdef.name try: session.system_sql(str('ALTER TABLE %s ADD COLUMN %s %s' % (table, column, attrtype)), @@ -425,13 +342,9 @@ except Exception, ex: self.error('error while creating index for %s.%s: %s', table, column, ex) - AddErdefOp(session, rdef) - -def after_add_efrdef(session, entity): - AddCWAttributePreCommitOp(session, entity=entity) -class AddCWRelationPreCommitOp(PreCommitOperation): +class SourceDbCWRelationAdd(SourceDbCWAttributeAdd): """an actual relation has been added: * if this is an inlined relation, add the necessary column else if it's the first instance of this relation type, add the @@ -442,43 +355,28 @@ constraints are handled by specific hooks """ entity = None # make pylint happy + def precommit_event(self): session = self.session entity = self.entity - fromentity = entity.stype - session.execute('SET X ordernum Y+1 ' - 'WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, ' - 'X ordernum >= %(order)s, NOT X eid %(x)s', - {'x': entity.eid, 'se': fromentity.eid, - 'order': entity.ordernum or 0}) - subj = str(fromentity.name) - rtype = entity.rtype.name - obj = str(entity.otype.name) - card = entity.get('cardinality') - rdef = RelationDefinition(subj, rtype, obj, - cardinality=card, - order=entity.ordernum, - composite=entity.composite, - description=entity.description, - constraints=get_constraints(session, entity), - eid=entity.eid) - schema = session.repo.schema - rschema = schema.rschema(rtype) + rdef = self.init_rdef(composite=entity.composite) + schema = session.schema + rtype = rdef.name + rschema = session.schema.rschema(rtype) # this have to be done before permissions setting - AddErdefOp(session, rdef) if rschema.inlined: # need to add a column if the relation is inlined and if this is the # first occurence of "Subject relation Something" whatever Something # and if it has not been added during other event of the same # transaction - key = '%s.%s' % (subj, rtype) + key = '%s.%s' % (rdef.subject, rtype) try: - alreadythere = bool(rschema.objects(subj)) + alreadythere = bool(rschema.objects(rdef.subject)) except KeyError: alreadythere = False if not (alreadythere or key in session.transaction_data.get('createdattrs', ())): - add_inline_relation_column(session, subj, rtype) + add_inline_relation_column(session, rdef.subject, rtype) else: # need to create the relation if no relation definition in the # schema and if it has not been added during other event of the same @@ -486,75 +384,24 @@ if not (rschema.subjects() or rtype in session.transaction_data.get('createdtables', ())): try: - rschema = schema[rtype] + rschema = session.schema.rschema(rtype) tablesql = rschema2sql(rschema) except KeyError: # fake we add it to the schema now to get a correctly # initialized schema but remove it before doing anything # more dangerous... - rschema = schema.add_relation_type(rdef) + rschema = session.schema.add_relation_type(rdef) tablesql = rschema2sql(rschema) - schema.del_relation_type(rtype) + session.schema.del_relation_type(rtype) # create the necessary table for sql in tablesql.split(';'): if sql.strip(): - self.session.system_sql(sql) + session.system_sql(sql) session.transaction_data.setdefault('createdtables', []).append( rtype) -def after_add_enfrdef(session, entity): - AddCWRelationPreCommitOp(session, entity=entity) - -# update ###################################################################### - -def check_valid_changes(session, entity, ro_attrs=('name', 'final')): - errors = {} - # don't use getattr(entity, attr), we would get the modified value if any - for attr in ro_attrs: - origval = entity_attr(session, entity.eid, attr) - if entity.get(attr, origval) != origval: - errors[attr] = session._("can't change the %s attribute") % \ - display_name(session, attr) - if errors: - raise ValidationError(entity.eid, errors) - -def before_update_eetype(session, entity): - """check name change, handle final""" - check_valid_changes(session, entity, ro_attrs=('final',)) - # don't use getattr(entity, attr), we would get the modified value if any - oldname = entity_attr(session, entity.eid, 'name') - newname = entity.get('name', oldname) - if newname.lower() != oldname.lower(): - eschema = session.repo.schema[oldname] - UpdateEntityTypeName(session, eschema=eschema, - oldname=oldname, newname=newname) - -def before_update_ertype(session, entity): - """check name change, handle final""" - check_valid_changes(session, entity) - - -class UpdateEntityTypeName(SchemaOperation): - """this operation updates physical storage accordingly""" - oldname = newname = None # make pylint happy - - def precommit_event(self): - # we need sql to operate physical changes on the system database - sqlexec = self.session.system_sql - sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, self.oldname, - SQL_PREFIX, self.newname)) - self.info('renamed table %s to %s', self.oldname, self.newname) - sqlexec('UPDATE entities SET type=%s WHERE type=%s', - (self.newname, self.oldname)) - sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s', - (self.newname, self.oldname)) - - def commit_event(self): - self.session.repo.schema.rename_entity_type(self.oldname, self.newname) - - -class UpdateRelationDefOp(SchemaOperation): +class SourceDbRDefUpdate(PreCommitOperation): """actually update some properties of a relation definition""" rschema = values = None # make pylint happy @@ -583,152 +430,38 @@ self.values['cardinality'][0] != '1') self.session.system_sql(sql) - def commit_event(self): - # structure should be clean, not need to remove entity's relations - # at this point - self.rschema._rproperties[self.kobj].update(self.values) - -def after_update_erdef(session, entity): - desttype = entity.otype.name - rschema = session.repo.schema[entity.rtype.name] - newvalues = {} - for prop in rschema.rproperty_defs(desttype): - if prop == 'constraints': - continue - if prop == 'order': - prop = 'ordernum' - if prop in entity: - newvalues[prop] = entity[prop] - if newvalues: - subjtype = entity.stype.name - UpdateRelationDefOp(session, (subjtype, desttype), - rschema=rschema, values=newvalues) - - -class UpdateRtypeOp(SchemaOperation): - """actually update some properties of a relation definition""" - rschema = values = entity = None # make pylint happy - - def precommit_event(self): - session = self.session - rschema = self.rschema - if rschema.is_final() or not 'inlined' in self.values: - return # nothing to do - inlined = self.values['inlined'] - entity = self.entity - if not entity.inlined_changed(inlined): # check in-lining is necessary/possible - return # nothing to do - # inlined changed, make necessary physical changes! - sqlexec = self.session.system_sql - rtype = rschema.type - eidcolumn = SQL_PREFIX + 'eid' - if not inlined: - # need to create the relation if it has not been already done by another - # event of the same transaction - if not rschema.type in session.transaction_data.get('createdtables', ()): - tablesql = rschema2sql(rschema) - # create the necessary table - for sql in tablesql.split(';'): - if sql.strip(): - sqlexec(sql) - session.transaction_data.setdefault('createdtables', []).append( - rschema.type) - # copy existant data - column = SQL_PREFIX + rtype - for etype in rschema.subjects(): - table = SQL_PREFIX + str(etype) - sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL' - % (rtype, eidcolumn, column, table, column)) - # drop existant columns - for etype in rschema.subjects(): - DropColumnOp(session, table=SQL_PREFIX + str(etype), - column=SQL_PREFIX + rtype) - else: - for etype in rschema.subjects(): - try: - add_inline_relation_column(session, str(etype), rtype) - except Exception, ex: - # the column probably already exists. this occurs when - # the entity's type has just been added or if the column - # has not been previously dropped - self.error('error while altering table %s: %s', etype, ex) - # copy existant data. - # XXX don't use, it's not supported by sqlite (at least at when i tried it) - #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to ' - # 'FROM %(rtype)s_relation ' - # 'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from' - # % locals()) - table = SQL_PREFIX + str(etype) - cursor = sqlexec('SELECT eid_from, eid_to FROM %(table)s, ' - '%(rtype)s_relation WHERE %(table)s.%(eidcolumn)s=' - '%(rtype)s_relation.eid_from' % locals()) - args = [{'val': eid_to, 'x': eid} for eid, eid_to in cursor.fetchall()] - if args: - column = SQL_PREFIX + rtype - cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s' - % (table, column, eidcolumn), args) - # drop existant table - DropTableOp(session, table='%s_relation' % rtype) - - def commit_event(self): - # structure should be clean, not need to remove entity's relations - # at this point - self.rschema.__dict__.update(self.values) - -def after_update_ertype(session, entity): - rschema = session.repo.schema.rschema(entity.name) - newvalues = {} - for prop in ('meta', 'symetric', 'inlined'): - if prop in entity: - newvalues[prop] = entity[prop] - if newvalues: - UpdateRtypeOp(session, entity=entity, rschema=rschema, values=newvalues) - -# constraints synchronization ################################################# - -from cubicweb.schema import CONSTRAINTS - -class ConstraintOp(SchemaOperation): +class SourceDbCWConstraintAdd(PreCommitOperation): """actually update constraint of a relation definition""" entity = None # make pylint happy - - def prepare_constraints(self, rtype, subjtype, objtype): - constraints = rtype.rproperty(subjtype, objtype, 'constraints') - self.constraints = list(constraints) - rtype.set_rproperty(subjtype, objtype, 'constraints', self.constraints) - return self.constraints + cancelled = False def precommit_event(self): rdef = self.entity.reverse_constrained_by[0] session = self.session - # when the relation is added in the same transaction, the constraint object - # is created by AddEN?FRDefPreCommitOp, there is nothing to do here + # when the relation is added in the same transaction, the constraint + # object is created by the operation adding the attribute or relation, + # so there is nothing to do here if rdef.eid in session.transaction_data.get('neweids', ()): - self.cancelled = True return - self.cancelled = False - schema = session.repo.schema - subjtype, rtype, objtype = schema.schema_by_eid(rdef.eid) - self.prepare_constraints(rtype, subjtype, objtype) + subjtype, rtype, objtype = session.schema.schema_by_eid(rdef.eid) cstrtype = self.entity.type - self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype) - self._cstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value) - self._cstr.eid = self.entity.eid + cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype) + prevcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value) table = SQL_PREFIX + str(subjtype) column = SQL_PREFIX + str(rtype) # alter the physical schema on size constraint changes - if self._cstr.type() == 'SizeConstraint' and ( - self.cstr is None or self.cstr.max != self._cstr.max): + if prevcstr.type() == 'SizeConstraint' and ( + cstr is None or cstr.max != prevcstr.max): adbh = self.session.pool.source('system').dbhelper card = rtype.rproperty(subjtype, objtype, 'cardinality') - coltype = type_from_constraints(adbh, objtype, [self._cstr], + coltype = type_from_constraints(adbh, objtype, [prevcstr], creating=False) sql = adbh.sql_change_col_type(table, column, coltype, card != '1') try: session.system_sql(sql, rollback_on_failure=False) self.info('altered column %s of table %s: now VARCHAR(%s)', - column, table, self._cstr.max) + column, table, prevcstr.max) except Exception, ex: # not supported by sqlite for instance self.error('error while altering table %s: %s', table, ex) @@ -736,28 +469,12 @@ session.pool.source('system').create_index( self.session, table, column, unique=True) - def commit_event(self): - if self.cancelled: - return - # in-place modification - if not self.cstr is None: - self.constraints.remove(self.cstr) - self.constraints.append(self._cstr) - -def after_add_econstraint(session, entity): - ConstraintOp(session, entity=entity) - -def after_update_econstraint(session, entity): - ConstraintOp(session, entity=entity) - - -class DelConstraintOp(ConstraintOp): +class SourceDbCWConstraintDel(PreCommitOperation): """actually remove a constraint of a relation definition""" rtype = subjtype = objtype = None # make pylint happy def precommit_event(self): - self.prepare_constraints(self.rtype, self.subjtype, self.objtype) cstrtype = self.cstr.type() table = SQL_PREFIX + str(self.subjtype) column = SQL_PREFIX + str(self.rtype) @@ -776,48 +493,143 @@ self.session.pool.source('system').drop_index( self.session, table, column, unique=True) + +# operations for in-memory schema synchronization ############################# + +class MemSchemaCWETypeAdd(MemSchemaEarlyOperation): + """actually add the entity type to the instance's schema""" + eid = None # make pylint happy + def commit_event(self): + self.schema.add_entity_type(self.kobj) + + +class MemSchemaCWETypeRename(MemSchemaOperation): + """this operation updates physical storage accordingly""" + oldname = newname = None # make pylint happy + + def commit_event(self): + self.session.schema.rename_entity_type(self.oldname, self.newname) + + +class MemSchemaCWETypeDel(MemSchemaOperation): + """actually remove the entity type from the instance's schema""" + def commit_event(self): + try: + # del_entity_type also removes entity's relations + self.schema.del_entity_type(self.kobj) + except KeyError: + # s/o entity type have already been deleted + pass + + +class MemSchemaCWRTypeAdd(MemSchemaEarlyOperation): + """actually add the relation type to the instance's schema""" + eid = None # make pylint happy + def commit_event(self): + rschema = self.schema.add_relation_type(self.kobj) + rschema.set_default_groups() + + +class MemSchemaCWRTypeUpdate(MemSchemaOperation): + """actually update some properties of a relation definition""" + rschema = values = None # make pylint happy + + def commit_event(self): + # structure should be clean, not need to remove entity's relations + # at this point + self.rschema.__dict__.update(self.values) + + +class MemSchemaCWRTypeDel(MemSchemaOperation): + """actually remove the relation type from the instance's schema""" + def commit_event(self): + try: + self.schema.del_relation_type(self.kobj) + except KeyError: + # s/o entity type have already been deleted + pass + + +class MemSchemaRDefAdd(MemSchemaEarlyOperation): + """actually add the attribute relation definition to the instance's + schema + """ + def commit_event(self): + self.schema.add_relation_def(self.kobj) + + +class MemSchemaRDefUpdate(MemSchemaOperation): + """actually update some properties of a relation definition""" + rschema = values = None # make pylint happy + + def commit_event(self): + # structure should be clean, not need to remove entity's relations + # at this point + self.rschema._rproperties[self.kobj].update(self.values) + + +class MemSchemaRDefDel(MemSchemaOperation): + """actually remove the relation definition from the instance's schema""" + def commit_event(self): + subjtype, rtype, objtype = self.kobj + try: + self.schema.del_relation_def(subjtype, rtype, objtype) + except KeyError: + # relation type may have been already deleted + pass + + +class MemSchemaCWConstraintAdd(MemSchemaOperation): + """actually update constraint of a relation definition + + has to be called before SourceDbCWConstraintAdd + """ + cancelled = False + + def precommit_event(self): + rdef = self.entity.reverse_constrained_by[0] + # when the relation is added in the same transaction, the constraint + # object is created by the operation adding the attribute or relation, + # so there is nothing to do here + if rdef.eid in self.session.transaction_data.get('neweids', ()): + self.cancelled = True + return + subjtype, rtype, objtype = self.session.schema.schema_by_eid(rdef.eid) + self.prepare_constraints(subjtype, rtype, objtype) + cstrtype = self.entity.type + self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype) + self.prevcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value) + self.prevcstr.eid = self.entity.eid + + def commit_event(self): + if self.cancelled: + return + # in-place modification + if not self.cstr is None: + self.constraints.remove(self.cstr) + self.constraints.append(self.prevcstr) + + +class MemSchemaCWConstraintDel(MemSchemaOperation): + """actually remove a constraint of a relation definition + + has to be called before SourceDbCWConstraintDel + """ + rtype = subjtype = objtype = None # make pylint happy + def precommit_event(self): + self.prepare_constraints(self.subjtype, self.rtype, self.objtype) + def commit_event(self): self.constraints.remove(self.cstr) -def before_delete_constrained_by(session, fromeid, rtype, toeid): - if not fromeid in session.transaction_data.get('pendingeids', ()): - schema = session.repo.schema - entity = session.eid_rset(toeid).get_entity(0, 0) - subjtype, rtype, objtype = schema.schema_by_eid(fromeid) - try: - cstr = rtype.constraint_by_type(subjtype, objtype, entity.cstrtype[0].name) - DelConstraintOp(session, subjtype=subjtype, rtype=rtype, objtype=objtype, - cstr=cstr) - except IndexError: - session.critical('constraint type no more accessible') - - -def after_add_constrained_by(session, fromeid, rtype, toeid): - if fromeid in session.transaction_data.get('neweids', ()): - session.transaction_data.setdefault(fromeid, []).append(toeid) - - -# schema permissions synchronization ########################################## - -class PermissionOp(Operation): - """base class to synchronize schema permission definitions""" - def __init__(self, session, perm, etype_eid): - self.perm = perm - try: - self.name = entity_name(session, etype_eid) - except IndexError: - self.error('changing permission of a no more existant type #%s', - etype_eid) - else: - Operation.__init__(self, session) - -class AddGroupPermissionOp(PermissionOp): +class MemSchemaPermissionCWGroupAdd(MemSchemaPermissionOperation): """synchronize schema when a *_permission relation has been added on a group """ def __init__(self, session, perm, etype_eid, group_eid): self.group = entity_name(session, group_eid) - PermissionOp.__init__(self, session, perm, etype_eid) + super(MemSchemaPermissionCWGroupAdd, self).__init__( + session, perm, etype_eid) def commit_event(self): """the observed connections pool has been commited""" @@ -836,40 +648,11 @@ groups.append(self.group) erschema.set_groups(self.perm, groups) -class AddRQLExpressionPermissionOp(PermissionOp): - """synchronize schema when a *_permission relation has been added on a rql - expression + +class MemSchemaPermissionCWGroupDel(MemSchemaPermissionCWGroupAdd): + """synchronize schema when a *_permission relation has been deleted from a + group """ - def __init__(self, session, perm, etype_eid, expression): - self.expr = expression - PermissionOp.__init__(self, session, perm, etype_eid) - - def commit_event(self): - """the observed connections pool has been commited""" - try: - erschema = self.schema[self.name] - except KeyError: - # duh, schema not found, log error and skip operation - self.error('no schema for %s', self.name) - return - exprs = list(erschema.get_rqlexprs(self.perm)) - exprs.append(erschema.rql_expression(self.expr)) - erschema.set_rqlexprs(self.perm, exprs) - -def after_add_permission(session, subject, rtype, object): - """added entity/relation *_permission, need to update schema""" - perm = rtype.split('_', 1)[0] - if session.describe(object)[0] == 'CWGroup': - AddGroupPermissionOp(session, perm, subject, object) - else: # RQLExpression - expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR', - {'x': object}, 'x')[0][0] - AddRQLExpressionPermissionOp(session, perm, subject, expr) - - - -class DelGroupPermissionOp(AddGroupPermissionOp): - """synchronize schema when a *_permission relation has been deleted from a group""" def commit_event(self): """the observed connections pool has been commited""" @@ -888,8 +671,32 @@ self.perm, erschema.type, self.group) -class DeleteRQLExpressionPermissionOp(AddRQLExpressionPermissionOp): - """synchronize schema when a *_permission relation has been deleted from an rql expression""" +class MemSchemaPermissionRQLExpressionAdd(MemSchemaPermissionOperation): + """synchronize schema when a *_permission relation has been added on a rql + expression + """ + def __init__(self, session, perm, etype_eid, expression): + self.expr = expression + super(MemSchemaPermissionRQLExpressionAdd, self).__init__( + session, perm, etype_eid) + + def commit_event(self): + """the observed connections pool has been commited""" + try: + erschema = self.schema[self.name] + except KeyError: + # duh, schema not found, log error and skip operation + self.error('no schema for %s', self.name) + return + exprs = list(erschema.get_rqlexprs(self.perm)) + exprs.append(erschema.rql_expression(self.expr)) + erschema.set_rqlexprs(self.perm, exprs) + + +class MemSchemaPermissionRQLExpressionDel(MemSchemaPermissionRQLExpressionAdd): + """synchronize schema when a *_permission relation has been deleted from an + rql expression + """ def commit_event(self): """the observed connections pool has been commited""" @@ -911,6 +718,283 @@ erschema.set_rqlexprs(self.perm, rqlexprs) +# deletion hooks ############################################################### + +def before_del_eetype(session, eid): + """before deleting a CWEType entity: + * check that we don't remove a core entity type + * cascade to delete related CWAttribute and CWRelation entities + * instantiate an operation to delete the entity type on commit + """ + # final entities can't be deleted, don't care about that + name = check_internal_entity(session, eid, CORE_ETYPES) + # delete every entities of this type + session.unsafe_execute('DELETE %s X' % name) + DropTable(session, table=SQL_PREFIX + name) + MemSchemaCWETypeDel(session, name) + + +def after_del_eetype(session, eid): + # workflow cleanup + session.execute('DELETE State X WHERE NOT X state_of Y') + session.execute('DELETE Transition X WHERE NOT X transition_of Y') + + +def before_del_ertype(session, eid): + """before deleting a CWRType entity: + * check that we don't remove a core relation type + * cascade to delete related CWAttribute and CWRelation entities + * instantiate an operation to delete the relation type on commit + """ + name = check_internal_entity(session, eid, CORE_RTYPES) + # delete relation definitions using this relation type + session.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s', + {'x': eid}) + session.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s', + {'x': eid}) + MemSchemaCWRTypeDel(session, name) + + +def after_del_relation_type(session, rdefeid, rtype, rteid): + """before deleting a CWAttribute or CWRelation entity: + * if this is a final or inlined relation definition, instantiate an + operation to drop necessary column, else if this is the last instance + of a non final relation, instantiate an operation to drop necessary + table + * instantiate an operation to delete the relation definition on commit + * delete the associated relation type when necessary + """ + subjschema, rschema, objschema = session.schema.schema_by_eid(rdefeid) + pendings = session.transaction_data.get('pendingeids', ()) + # first delete existing relation if necessary + if rschema.is_final(): + rdeftype = 'CWAttribute' + else: + rdeftype = 'CWRelation' + if not (subjschema.eid in pendings or objschema.eid in pendings): + session.execute('DELETE X %s Y WHERE X is %s, Y is %s' + % (rschema, subjschema, objschema)) + execute = session.unsafe_execute + rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,' + 'R eid %%(x)s' % rdeftype, {'x': rteid}) + lastrel = rset[0][0] == 0 + # we have to update physical schema systematically for final and inlined + # relations, but only if it's the last instance for this relation type + # for other relations + + if (rschema.is_final() or rschema.inlined): + rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, ' + 'R eid %%(x)s, X from_entity E, E name %%(name)s' + % rdeftype, {'x': rteid, 'name': str(subjschema)}) + if rset[0][0] == 0 and not subjschema.eid in pendings: + ptypes = session.transaction_data.setdefault('pendingrtypes', set()) + ptypes.add(rschema.type) + DropColumn(session, table=SQL_PREFIX + subjschema.type, + column=SQL_PREFIX + rschema.type) + elif lastrel: + DropRelationTable(session, rschema.type) + # if this is the last instance, drop associated relation type + if lastrel and not rteid in pendings: + execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x') + MemSchemaRDefDel(session, (subjschema, rschema, objschema)) + + +# addition hooks ############################################################### + +def before_add_eetype(session, entity): + """before adding a CWEType entity: + * check that we are not using an existing entity type, + """ + name = entity['name'] + schema = session.schema + if name in schema and schema[name].eid is not None: + raise RepositoryError('an entity type %s already exists' % name) + +def after_add_eetype(session, entity): + """after adding a CWEType entity: + * create the necessary table + * set creation_date and modification_date by creating the necessary + CWAttribute entities + * add owned_by relation by creating the necessary CWRelation entity + * register an operation to add the entity type to the instance's + schema on commit + """ + if entity.get('final'): + return + schema = session.schema + name = entity['name'] + etype = EntityType(name=name, description=entity.get('description'), + meta=entity.get('meta')) # don't care about final + # fake we add it to the schema now to get a correctly initialized schema + # but remove it before doing anything more dangerous... + schema = session.schema + eschema = schema.add_entity_type(etype) + eschema.set_default_groups() + # generate table sql and rql to add metadata + tablesql = eschema2sql(session.pool.source('system').dbhelper, eschema, + prefix=SQL_PREFIX) + relrqls = [] + for rtype in (META_RTYPES - VIRTUAL_RTYPES): + rschema = schema[rtype] + sampletype = rschema.subjects()[0] + desttype = rschema.objects()[0] + props = rschema.rproperties(sampletype, desttype) + relrqls += list(ss.rdef2rql(rschema, name, desttype, props)) + # now remove it ! + schema.del_entity_type(name) + # create the necessary table + for sql in tablesql.split(';'): + if sql.strip(): + session.system_sql(sql) + # register operation to modify the schema on commit + # this have to be done before adding other relations definitions + # or permission settings + etype.eid = entity.eid + MemSchemaCWETypeAdd(session, etype) + # add meta relations + for rql, kwargs in relrqls: + session.execute(rql, kwargs) + + +def before_add_ertype(session, entity): + """before adding a CWRType entity: + * check that we are not using an existing relation type, + * register an operation to add the relation type to the instance's + schema on commit + + We don't know yeat this point if a table is necessary + """ + name = entity['name'] + if name in session.schema.relations(): + raise RepositoryError('a relation type %s already exists' % name) + + +def after_add_ertype(session, entity): + """after a CWRType entity has been added: + * register an operation to add the relation type to the instance's + schema on commit + We don't know yeat this point if a table is necessary + """ + rtype = RelationType(name=entity['name'], + description=entity.get('description'), + meta=entity.get('meta', False), + inlined=entity.get('inlined', False), + symetric=entity.get('symetric', False)) + rtype.eid = entity.eid + MemSchemaCWRTypeAdd(session, rtype) + + +def after_add_efrdef(session, entity): + SourceDbCWAttributeAdd(session, entity=entity) + +def after_add_enfrdef(session, entity): + SourceDbCWRelationAdd(session, entity=entity) + + +# update hooks ################################################################# + +def check_valid_changes(session, entity, ro_attrs=('name', 'final')): + errors = {} + # don't use getattr(entity, attr), we would get the modified value if any + for attr in ro_attrs: + origval = entity_attr(session, entity.eid, attr) + if entity.get(attr, origval) != origval: + errors[attr] = session._("can't change the %s attribute") % \ + display_name(session, attr) + if errors: + raise ValidationError(entity.eid, errors) + +def before_update_eetype(session, entity): + """check name change, handle final""" + check_valid_changes(session, entity, ro_attrs=('final',)) + # don't use getattr(entity, attr), we would get the modified value if any + oldname = entity_attr(session, entity.eid, 'name') + newname = entity.get('name', oldname) + if newname.lower() != oldname.lower(): + SourceDbCWETypeRename(session, oldname=oldname, newname=newname) + MemSchemaCWETypeRename(session, oldname=oldname, newname=newname) + +def before_update_ertype(session, entity): + """check name change, handle final""" + check_valid_changes(session, entity) + + +def after_update_erdef(session, entity): + desttype = entity.otype.name + rschema = session.schema[entity.rtype.name] + newvalues = {} + for prop in rschema.rproperty_defs(desttype): + if prop == 'constraints': + continue + if prop == 'order': + prop = 'ordernum' + if prop in entity: + newvalues[prop] = entity[prop] + if newvalues: + subjtype = entity.stype.name + MemSchemaRDefUpdate(session, kobj=(subjtype, desttype), + rschema=rschema, values=newvalues) + SourceDbRDefUpdate(session, kobj=(subjtype, desttype), + rschema=rschema, values=newvalues) + +def after_update_ertype(session, entity): + rschema = session.schema.rschema(entity.name) + newvalues = {} + for prop in ('meta', 'symetric', 'inlined'): + if prop in entity: + newvalues[prop] = entity[prop] + if newvalues: + MemSchemaCWRTypeUpdate(session, rschema=rschema, values=newvalues) + SourceDbCWRTypeUpdate(session, rschema=rschema, values=newvalues, + entity=entity) + +# constraints synchronization hooks ############################################ + +def after_add_econstraint(session, entity): + MemSchemaCWConstraintAdd(session, entity=entity) + SourceDbCWConstraintAdd(session, entity=entity) + + +def after_update_econstraint(session, entity): + MemSchemaCWConstraintAdd(session, entity=entity) + SourceDbCWConstraintAdd(session, entity=entity) + + +def before_delete_constrained_by(session, fromeid, rtype, toeid): + if not fromeid in session.transaction_data.get('pendingeids', ()): + schema = session.schema + entity = session.eid_rset(toeid).get_entity(0, 0) + subjtype, rtype, objtype = schema.schema_by_eid(fromeid) + try: + cstr = rtype.constraint_by_type(subjtype, objtype, + entity.cstrtype[0].name) + except IndexError: + session.critical('constraint type no more accessible') + else: + SourceDbCWConstraintDel(session, subjtype=subjtype, rtype=rtype, + objtype=objtype, cstr=cstr) + MemSchemaCWConstraintDel(session, subjtype=subjtype, rtype=rtype, + objtype=objtype, cstr=cstr) + + +def after_add_constrained_by(session, fromeid, rtype, toeid): + if fromeid in session.transaction_data.get('neweids', ()): + session.transaction_data.setdefault(fromeid, []).append(toeid) + + +# permissions synchronization hooks ############################################ + +def after_add_permission(session, subject, rtype, object): + """added entity/relation *_permission, need to update schema""" + perm = rtype.split('_', 1)[0] + if session.describe(object)[0] == 'CWGroup': + MemSchemaPermissionCWGroupAdd(session, perm, subject, object) + else: # RQLExpression + expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR', + {'x': object}, 'x')[0][0] + MemSchemaPermissionRQLExpressionAdd(session, perm, subject, expr) + + def before_del_permission(session, subject, rtype, object): """delete entity/relation *_permission, need to update schema @@ -920,18 +1004,18 @@ return perm = rtype.split('_', 1)[0] if session.describe(object)[0] == 'CWGroup': - DelGroupPermissionOp(session, perm, subject, object) + MemSchemaPermissionCWGroupDel(session, perm, subject, object) else: # RQLExpression expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR', {'x': object}, 'x')[0][0] - DeleteRQLExpressionPermissionOp(session, perm, subject, expr) + MemSchemaPermissionRQLExpressionDel(session, perm, subject, expr) def rebuild_infered_relations(session, subject, rtype, object): # registering a schema operation will trigger a call to # repo.set_schema() on commit which will in turn rebuild # infered relation definitions - UpdateSchemaOp(session) + MemSchemaNotifyChanges(session) def _register_schema_hooks(hm):