--- a/server/migractions.py Fri Aug 21 15:04:35 2009 +0200
+++ b/server/migractions.py Fri Aug 21 15:05:50 2009 +0200
@@ -61,6 +61,7 @@
assert repo
self._cnx = cnx
self.repo = repo
+ self.session.data['rebuild-infered'] = False
elif connect:
self.repo_connect()
if not schema:
@@ -222,6 +223,7 @@
print 'aborting...'
sys.exit(0)
self.session.keep_pool_mode('transaction')
+ self.session.data['rebuild-infered'] = False
return self._cnx
@property
@@ -626,11 +628,13 @@
in auto mode, automatically register entity's relation where the
targeted type is known
"""
- applschema = self.repo.schema
- if etype in applschema:
- eschema = applschema[etype]
+ instschema = self.repo.schema
+ if etype in instschema:
+ # XXX (syt) plz explain: if we're adding an entity type, it should
+ # not be there...
+ eschema = instschema[etype]
if eschema.is_final():
- applschema.del_entity_type(etype)
+ instschema.del_entity_type(etype)
else:
eschema = self.fs_schema.eschema(etype)
confirm = self.verbosity >= 2
@@ -646,13 +650,46 @@
# ignore those meta relations, they will be automatically added
if rschema.type in META_RTYPES:
continue
- if not rschema.type in applschema:
+ if not rschema.type in instschema:
# need to add the relation type and to commit to get it
# actually in the schema
self.cmd_add_relation_type(rschema.type, False, commit=True)
# register relation definition
self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type),
ask_confirm=confirm)
+ # take care to newly introduced base class
+ # XXX some part of this should probably be under the "if auto" block
+ for spschema in eschema.specialized_by(recursive=False):
+ try:
+ instspschema = instschema[spschema]
+ except KeyError:
+ # specialized entity type not in schema, ignore
+ continue
+ if instspschema.specializes() != eschema:
+ self.rqlexec('SET D specializes P WHERE D eid %(d)s, P name %(pn)s',
+ {'d': instspschema.eid,
+ 'pn': eschema.type}, ask_confirm=confirm)
+ for rschema, tschemas, role in spschema.relation_definitions(True):
+ for tschema in tschemas:
+ if not tschema in instschema:
+ continue
+ if role == 'subject':
+ subjschema = spschema
+ objschema = tschema
+ if rschema.final and instspschema.has_subject_relation(rschema):
+ # attribute already set, has_rdef would check if
+ # it's of the same type, we don't want this so
+ # simply skip here
+ continue
+ elif role == 'object':
+ subjschema = tschema
+ objschema = spschema
+ if (rschema.rproperty(subjschema, objschema, 'infered')
+ or (instschema.has_relation(rschema) and
+ instschema[rschema].has_rdef(subjschema, objschema))):
+ continue
+ self.cmd_add_relation_definition(
+ subjschema.type, rschema.type, objschema.type)
if auto:
# we have commit here to get relation types actually in the schema
self.commit()
@@ -662,12 +699,12 @@
# 'owned_by'/'created_by' will be automatically added
if rschema.final or rschema.type in META_RTYPES:
continue
- rtypeadded = rschema.type in applschema
+ rtypeadded = rschema.type in instschema
for targetschema in rschema.objects(etype):
# ignore relations where the targeted type is not in the
# current instance schema
targettype = targetschema.type
- if not targettype in applschema and targettype != etype:
+ if not targettype in instschema and targettype != etype:
continue
if not rtypeadded:
# need to add the relation type and to commit to get it
@@ -682,14 +719,14 @@
self.rqlexecall(ss.rdef2rql(rschema, etype, targettype),
ask_confirm=confirm)
for rschema in eschema.object_relations():
- rtypeadded = rschema.type in applschema or rschema.type in added
+ rtypeadded = rschema.type in instschema or rschema.type in added
for targetschema in rschema.subjects(etype):
# ignore relations where the targeted type is not in the
# current instance schema
targettype = targetschema.type
# don't check targettype != etype since in this case the
# relation has already been added as a subject relation
- if not targettype in applschema:
+ if not targettype in instschema:
continue
if not rtypeadded:
# need to add the relation type and to commit to get it
--- a/server/repository.py Fri Aug 21 15:04:35 2009 +0200
+++ b/server/repository.py Fri Aug 21 15:05:50 2009 +0200
@@ -240,8 +240,9 @@
source_config['uri'] = uri
return get_source(source_config, self.schema, self)
- def set_schema(self, schema, resetvreg=True):
- schema.rebuild_infered_relations()
+ def set_schema(self, schema, resetvreg=True, rebuildinfered=True):
+ if rebuildinfered:
+ schema.rebuild_infered_relations()
self.info('set schema %s %#x', schema.name, id(schema))
self.debug(', '.join(sorted(str(e) for e in schema.entities())))
self.querier.set_schema(schema)
--- a/server/schemahooks.py Fri Aug 21 15:04:35 2009 +0200
+++ b/server/schemahooks.py Fri Aug 21 15:05:50 2009 +0200
@@ -135,7 +135,8 @@
SingleLastOperation.__init__(self, session)
def commit_event(self):
- self.repo.set_schema(self.repo.schema)
+ rebuildinfered = self.session.data.get('rebuild-infered', True)
+ self.repo.set_schema(self.repo.schema, rebuildinfered=rebuildinfered)
class MemSchemaOperation(Operation):
@@ -718,6 +719,28 @@
erschema.set_rqlexprs(self.perm, rqlexprs)
+class MemSchemaSpecializesAdd(MemSchemaOperation):
+
+ def commit_event(self):
+ eschema = self.session.schema.schema_by_eid(self.etypeeid)
+ parenteschema = self.session.schema.schema_by_eid(self.parentetypeeid)
+ eschema._specialized_type = parenteschema.type
+ parenteschema._specialized_by.append(eschema.type)
+
+
+class MemSchemaSpecializesDel(MemSchemaOperation):
+
+ def commit_event(self):
+ try:
+ eschema = self.session.schema.schema_by_eid(self.etypeeid)
+ parenteschema = self.session.schema.schema_by_eid(self.parentetypeeid)
+ except KeyError:
+ # etype removed, nothing to do
+ return
+ eschema._specialized_type = None
+ parenteschema._specialized_by.remove(eschema.type)
+
+
# deletion hooks ###############################################################
def before_del_eetype(session, eid):
@@ -1015,11 +1038,11 @@
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
- MemSchemaNotifyChanges(session)
+def after_add_specializes(session, subject, rtype, object):
+ MemSchemaSpecializesAdd(session, etypeeid=subject, parentetypeeid=object)
+
+def after_del_specializes(session, subject, rtype, object):
+ MemSchemaSpecializesDel(session, etypeeid=subject, parentetypeeid=object)
def _register_schema_hooks(hm):
@@ -1043,8 +1066,8 @@
hm.register_hook(after_del_eetype, 'after_delete_entity', 'CWEType')
hm.register_hook(before_del_ertype, 'before_delete_entity', 'CWRType')
hm.register_hook(after_del_relation_type, 'after_delete_relation', 'relation_type')
- hm.register_hook(rebuild_infered_relations, 'after_add_relation', 'specializes')
- hm.register_hook(rebuild_infered_relations, 'after_delete_relation', 'specializes')
+ hm.register_hook(after_add_specializes, 'after_add_relation', 'specializes')
+ hm.register_hook(after_del_specializes, 'after_delete_relation', 'specializes')
# constraints synchronization hooks
hm.register_hook(after_add_econstraint, 'after_add_entity', 'CWConstraint')
hm.register_hook(after_update_econstraint, 'after_update_entity', 'CWConstraint')
--- a/server/test/data/migratedapp/schema.py Fri Aug 21 15:04:35 2009 +0200
+++ b/server/test/data/migratedapp/schema.py Fri Aug 21 15:05:50 2009 +0200
@@ -33,7 +33,15 @@
'delete': ('managers', RRQLExpression('O owned_by U')),
}
-class Note(EntityType):
+class Para(EntityType):
+ para = String(maxsize=512)
+ newattr = String()
+ newinlined = SubjectRelation('Affaire', cardinality='?*', inlined=True)
+ newnotinlined = SubjectRelation('Affaire', cardinality='?*')
+
+class Note(Para):
+ __specializes_schema__ = True
+
permissions = {'read': ('managers', 'users', 'guests',),
'update': ('managers', 'owners',),
'delete': ('managers', ),
@@ -46,11 +54,14 @@
type = String(maxsize=1)
whatever = Int()
mydate = Date(default='TODAY')
- para = String(maxsize=512)
shortpara = String(maxsize=64)
ecrit_par = SubjectRelation('Personne', constraints=[RQLConstraint('S concerne A, O concerne A')])
attachment = SubjectRelation(('File', 'Image'))
+class Text(Para):
+ __specializes_schema__ = True
+ summary = String(maxsize=512)
+
class ecrit_par(RelationType):
permissions = {'read': ('managers', 'users', 'guests',),
'delete': ('managers', ),
--- a/server/test/unittest_migractions.py Fri Aug 21 15:04:35 2009 +0200
+++ b/server/test/unittest_migractions.py Fri Aug 21 15:05:50 2009 +0200
@@ -457,5 +457,47 @@
user.clear_related_cache('in_state', 'subject')
self.assertEquals(user.state, 'deactivated')
+ def test_introduce_base_class(self):
+ self.mh.cmd_add_entity_type('Para')
+ self.mh.repo.schema.rebuild_infered_relations()
+ self.assertEquals(sorted(et.type for et in self.schema['Para'].specialized_by()),
+ ['Note'])
+ self.assertEquals(self.schema['Note'].specializes().type, 'Para')
+ self.mh.cmd_add_entity_type('Text')
+ self.mh.repo.schema.rebuild_infered_relations()
+ self.assertEquals(sorted(et.type for et in self.schema['Para'].specialized_by()),
+ ['Note', 'Text'])
+ self.assertEquals(self.schema['Text'].specializes().type, 'Para')
+ # test columns have been actually added
+ text = self.execute('INSERT Text X: X para "hip", X summary "hop", X newattr "momo"').get_entity(0, 0)
+ note = self.execute('INSERT Note X: X para "hip", X shortpara "hop", X newattr "momo"').get_entity(0, 0)
+ aff = self.execute('INSERT Affaire X').get_entity(0, 0)
+ self.failUnless(self.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s',
+ {'x': text.eid, 'y': aff.eid}, 'x'))
+ self.failUnless(self.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s',
+ {'x': note.eid, 'y': aff.eid}, 'x'))
+ self.failUnless(self.execute('SET X newinlined Y WHERE X eid %(x)s, Y eid %(y)s',
+ {'x': text.eid, 'y': aff.eid}, 'x'))
+ self.failUnless(self.execute('SET X newinlined Y WHERE X eid %(x)s, Y eid %(y)s',
+ {'x': note.eid, 'y': aff.eid}, 'x'))
+ # XXX remove specializes by ourselves, else tearDown fails when removing
+ # Para because of Note inheritance. This could be fixed by putting the
+ # MemSchemaCWETypeDel(session, name) operation in the
+ # after_delete_entity(CWEType) hook, since in that case the MemSchemaSpecializesDel
+ # operation would be removed before, but I'm not sure this is a desired behaviour.
+ #
+ # also we need more tests about introducing/removing base classes or
+ # specialization relationship...
+ self.session.data['rebuild-infered'] = True
+ try:
+ self.execute('DELETE X specializes Y WHERE Y name "Para"')
+ self.commit()
+ finally:
+ self.session.data['rebuild-infered'] = False
+ self.assertEquals(sorted(et.type for et in self.schema['Para'].specialized_by()),
+ [])
+ self.assertEquals(self.schema['Note'].specializes(), None)
+ self.assertEquals(self.schema['Text'].specializes(), None)
+
if __name__ == '__main__':
unittest_main()