hooks/syncschema.py
changeset 9621 202c4797e365
parent 9548 be001628edad
child 9635 aaf099172bb9
equal deleted inserted replaced
9620:7b7d5a5d6365 9621:202c4797e365
    42 # core entity and relation types which can't be removed
    42 # core entity and relation types which can't be removed
    43 CORE_TYPES = BASE_TYPES | SCHEMA_TYPES | META_RTYPES | set(
    43 CORE_TYPES = BASE_TYPES | SCHEMA_TYPES | META_RTYPES | set(
    44     ('CWUser', 'CWGroup','login', 'upassword', 'name', 'in_group'))
    44     ('CWUser', 'CWGroup','login', 'upassword', 'name', 'in_group'))
    45 
    45 
    46 
    46 
    47 def get_constraints(session, entity):
    47 def get_constraints(cnx, entity):
    48     constraints = []
    48     constraints = []
    49     for cstreid in session.transaction_data.get(entity.eid, ()):
    49     for cstreid in cnx.transaction_data.get(entity.eid, ()):
    50         cstrent = session.entity_from_eid(cstreid)
    50         cstrent = cnx.entity_from_eid(cstreid)
    51         cstr = CONSTRAINTS[cstrent.type].deserialize(cstrent.value)
    51         cstr = CONSTRAINTS[cstrent.type].deserialize(cstrent.value)
    52         cstr.eid = cstreid
    52         cstr.eid = cstreid
    53         constraints.append(cstr)
    53         constraints.append(cstr)
    54     return constraints
    54     return constraints
    55 
    55 
    58         return cw.transaction_data['groupmap']
    58         return cw.transaction_data['groupmap']
    59     except KeyError:
    59     except KeyError:
    60         cw.transaction_data['groupmap'] = gmap = ss.group_mapping(cw)
    60         cw.transaction_data['groupmap'] = gmap = ss.group_mapping(cw)
    61         return gmap
    61         return gmap
    62 
    62 
    63 def add_inline_relation_column(session, etype, rtype):
    63 def add_inline_relation_column(cnx, etype, rtype):
    64     """add necessary column and index for an inlined relation"""
    64     """add necessary column and index for an inlined relation"""
    65     attrkey = '%s.%s' % (etype, rtype)
    65     attrkey = '%s.%s' % (etype, rtype)
    66     createdattrs = session.transaction_data.setdefault('createdattrs', set())
    66     createdattrs = cnx.transaction_data.setdefault('createdattrs', set())
    67     if attrkey in createdattrs:
    67     if attrkey in createdattrs:
    68         return
    68         return
    69     createdattrs.add(attrkey)
    69     createdattrs.add(attrkey)
    70     table = SQL_PREFIX + etype
    70     table = SQL_PREFIX + etype
    71     column = SQL_PREFIX + rtype
    71     column = SQL_PREFIX + rtype
    72     try:
    72     try:
    73         session.system_sql(str('ALTER TABLE %s ADD %s integer'
    73         cnx.system_sql(str('ALTER TABLE %s ADD %s integer'
    74                                % (table, column)), rollback_on_failure=False)
    74                                % (table, column)), rollback_on_failure=False)
    75         session.info('added column %s to table %s', column, table)
    75         cnx.info('added column %s to table %s', column, table)
    76     except Exception:
    76     except Exception:
    77         # silent exception here, if this error has not been raised because the
    77         # silent exception here, if this error has not been raised because the
    78         # column already exists, index creation will fail anyway
    78         # column already exists, index creation will fail anyway
    79         session.exception('error while adding column %s to table %s',
    79         cnx.exception('error while adding column %s to table %s',
    80                           table, column)
    80                           table, column)
    81     # create index before alter table which may expectingly fail during test
    81     # create index before alter table which may expectingly fail during test
    82     # (sqlite) while index creation should never fail (test for index existence
    82     # (sqlite) while index creation should never fail (test for index existence
    83     # is done by the dbhelper)
    83     # is done by the dbhelper)
    84     session.repo.system_source.create_index(session, table, column)
    84     cnx.repo.system_source.create_index(cnx, table, column)
    85     session.info('added index on %s(%s)', table, column)
    85     cnx.info('added index on %s(%s)', table, column)
    86 
    86 
    87 
    87 
    88 def insert_rdef_on_subclasses(session, eschema, rschema, rdefdef, props):
    88 def insert_rdef_on_subclasses(cnx, eschema, rschema, rdefdef, props):
    89     # XXX 'infered': True/False, not clear actually
    89     # XXX 'infered': True/False, not clear actually
    90     props.update({'constraints': rdefdef.constraints,
    90     props.update({'constraints': rdefdef.constraints,
    91                   'description': rdefdef.description,
    91                   'description': rdefdef.description,
    92                   'cardinality': rdefdef.cardinality,
    92                   'cardinality': rdefdef.cardinality,
    93                   'permissions': rdefdef.get_permissions(),
    93                   'permissions': rdefdef.get_permissions(),
    94                   'order': rdefdef.order,
    94                   'order': rdefdef.order,
    95                   'infered': False, 'eid': None
    95                   'infered': False, 'eid': None
    96                   })
    96                   })
    97     cstrtypemap = ss.cstrtype_mapping(session)
    97     cstrtypemap = ss.cstrtype_mapping(cnx)
    98     groupmap = group_mapping(session)
    98     groupmap = group_mapping(cnx)
    99     object = rschema.schema.eschema(rdefdef.object)
    99     object = rschema.schema.eschema(rdefdef.object)
   100     for specialization in eschema.specialized_by(False):
   100     for specialization in eschema.specialized_by(False):
   101         if (specialization, rdefdef.object) in rschema.rdefs:
   101         if (specialization, rdefdef.object) in rschema.rdefs:
   102             continue
   102             continue
   103         sperdef = RelationDefinitionSchema(specialization, rschema,
   103         sperdef = RelationDefinitionSchema(specialization, rschema,
   104                                            object, None, values=props)
   104                                            object, None, values=props)
   105         ss.execschemarql(session.execute, sperdef,
   105         ss.execschemarql(cnx.execute, sperdef,
   106                          ss.rdef2rql(sperdef, cstrtypemap, groupmap))
   106                          ss.rdef2rql(sperdef, cstrtypemap, groupmap))
   107 
   107 
   108 
   108 
   109 def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
   109 def check_valid_changes(cnx, entity, ro_attrs=('name', 'final')):
   110     errors = {}
   110     errors = {}
   111     # don't use getattr(entity, attr), we would get the modified value if any
   111     # don't use getattr(entity, attr), we would get the modified value if any
   112     for attr in entity.cw_edited:
   112     for attr in entity.cw_edited:
   113         if attr in ro_attrs:
   113         if attr in ro_attrs:
   114             origval, newval = entity.cw_edited.oldnewvalue(attr)
   114             origval, newval = entity.cw_edited.oldnewvalue(attr)
   135 
   135 
   136 class DropTable(hook.Operation):
   136 class DropTable(hook.Operation):
   137     """actually remove a database from the instance's schema"""
   137     """actually remove a database from the instance's schema"""
   138     table = None # make pylint happy
   138     table = None # make pylint happy
   139     def precommit_event(self):
   139     def precommit_event(self):
   140         dropped = self.session.transaction_data.setdefault('droppedtables',
   140         dropped = self.cnx.transaction_data.setdefault('droppedtables',
   141                                                            set())
   141                                                            set())
   142         if self.table in dropped:
   142         if self.table in dropped:
   143             return # already processed
   143             return # already processed
   144         dropped.add(self.table)
   144         dropped.add(self.table)
   145         self.session.system_sql('DROP TABLE %s' % self.table)
   145         self.cnx.system_sql('DROP TABLE %s' % self.table)
   146         self.info('dropped table %s', self.table)
   146         self.info('dropped table %s', self.table)
   147 
   147 
   148     # XXX revertprecommit_event
   148     # XXX revertprecommit_event
   149 
   149 
   150 
   150 
   151 class DropRelationTable(DropTable):
   151 class DropRelationTable(DropTable):
   152     def __init__(self, session, rtype):
   152     def __init__(self, cnx, rtype):
   153         super(DropRelationTable, self).__init__(
   153         super(DropRelationTable, self).__init__(
   154             session, table='%s_relation' % rtype)
   154             cnx, table='%s_relation' % rtype)
   155         session.transaction_data.setdefault('pendingrtypes', set()).add(rtype)
   155         cnx.transaction_data.setdefault('pendingrtypes', set()).add(rtype)
   156 
   156 
   157 
   157 
   158 class DropColumn(hook.Operation):
   158 class DropColumn(hook.Operation):
   159     """actually remove the attribut's column from entity table in the system
   159     """actually remove the attribut's column from entity table in the system
   160     database
   160     database
   161     """
   161     """
   162     table = column = None # make pylint happy
   162     table = column = None # make pylint happy
   163     def precommit_event(self):
   163     def precommit_event(self):
   164         session, table, column = self.session, self.table, self.column
   164         cnx, table, column = self.cnx, self.table, self.column
   165         source = session.repo.system_source
   165         source = cnx.repo.system_source
   166         # drop index if any
   166         # drop index if any
   167         source.drop_index(session, table, column)
   167         source.drop_index(cnx, table, column)
   168         if source.dbhelper.alter_column_support:
   168         if source.dbhelper.alter_column_support:
   169             session.system_sql('ALTER TABLE %s DROP COLUMN %s'
   169             cnx.system_sql('ALTER TABLE %s DROP COLUMN %s'
   170                                % (table, column), rollback_on_failure=False)
   170                                % (table, column), rollback_on_failure=False)
   171             self.info('dropped column %s from table %s', column, table)
   171             self.info('dropped column %s from table %s', column, table)
   172         else:
   172         else:
   173             # not supported by sqlite for instance
   173             # not supported by sqlite for instance
   174             self.error('dropping column not supported by the backend, handle '
   174             self.error('dropping column not supported by the backend, handle '
   185     special operation which should be called once and after all other schema
   185     special operation which should be called once and after all other schema
   186     operations. It will trigger internal structures rebuilding to consider
   186     operations. It will trigger internal structures rebuilding to consider
   187     schema changes.
   187     schema changes.
   188     """
   188     """
   189 
   189 
   190     def __init__(self, session):
   190     def __init__(self, cnx):
   191         hook.SingleLastOperation.__init__(self, session)
   191         hook.SingleLastOperation.__init__(self, cnx)
   192 
   192 
   193     def precommit_event(self):
   193     def precommit_event(self):
   194         for eschema in self.session.repo.schema.entities():
   194         for eschema in self.cnx.repo.schema.entities():
   195             if not eschema.final:
   195             if not eschema.final:
   196                 clear_cache(eschema, 'ordered_relations')
   196                 clear_cache(eschema, 'ordered_relations')
   197 
   197 
   198     def postcommit_event(self):
   198     def postcommit_event(self):
   199         rebuildinfered = self.session.get_shared_data('rebuild-infered', True)
   199         rebuildinfered = self.cnx.get_shared_data('rebuild-infered', True)
   200         repo = self.session.repo
   200         repo = self.cnx.repo
   201         # commit event should not raise error, while set_schema has chances to
   201         # commit event should not raise error, while set_schema has chances to
   202         # do so because it triggers full vreg reloading
   202         # do so because it triggers full vreg reloading
   203         try:
   203         try:
   204             if rebuildinfered:
   204             if rebuildinfered:
   205                 repo.schema.rebuild_infered_relations()
   205                 repo.schema.rebuild_infered_relations()
   206             # trigger vreg reload
   206             # trigger vreg reload
   207             repo.set_schema(repo.schema)
   207             repo.set_schema(repo.schema)
   208             # CWUser class might have changed, update current session users
   208             # CWUser class might have changed, update current session users
   209             cwuser_cls = self.session.vreg['etypes'].etype_class('CWUser')
   209             cwuser_cls = self.cnx.vreg['etypes'].etype_class('CWUser')
   210             for session in repo._sessions.itervalues():
   210             for session in repo._sessions.itervalues():
   211                 session.user.__class__ = cwuser_cls
   211                 session.user.__class__ = cwuser_cls
   212         except Exception:
   212         except Exception:
   213             self.critical('error while setting schema', exc_info=True)
   213             self.critical('error while setting schema', exc_info=True)
   214 
   214 
   216         self.precommit_event()
   216         self.precommit_event()
   217 
   217 
   218 
   218 
   219 class MemSchemaOperation(hook.Operation):
   219 class MemSchemaOperation(hook.Operation):
   220     """base class for schema operations"""
   220     """base class for schema operations"""
   221     def __init__(self, session, **kwargs):
   221     def __init__(self, cnx, **kwargs):
   222         hook.Operation.__init__(self, session, **kwargs)
   222         hook.Operation.__init__(self, cnx, **kwargs)
   223         # every schema operation is triggering a schema update
   223         # every schema operation is triggering a schema update
   224         MemSchemaNotifyChanges(session)
   224         MemSchemaNotifyChanges(cnx)
   225 
   225 
   226 
   226 
   227 # operations for high-level source database alteration  ########################
   227 # operations for high-level source database alteration  ########################
   228 
   228 
   229 class CWETypeAddOp(MemSchemaOperation):
   229 class CWETypeAddOp(MemSchemaOperation):
   235     * add <meta rtype> relation by creating the necessary CWRelation entity
   235     * add <meta rtype> relation by creating the necessary CWRelation entity
   236     """
   236     """
   237     entity = None # make pylint happy
   237     entity = None # make pylint happy
   238 
   238 
   239     def precommit_event(self):
   239     def precommit_event(self):
   240         session = self.session
   240         cnx = self.cnx
   241         entity = self.entity
   241         entity = self.entity
   242         schema = session.vreg.schema
   242         schema = cnx.vreg.schema
   243         etype = ybo.EntityType(eid=entity.eid, name=entity.name,
   243         etype = ybo.EntityType(eid=entity.eid, name=entity.name,
   244                                description=entity.description)
   244                                description=entity.description)
   245         eschema = schema.add_entity_type(etype)
   245         eschema = schema.add_entity_type(etype)
   246         # create the necessary table
   246         # create the necessary table
   247         tablesql = y2sql.eschema2sql(session.repo.system_source.dbhelper,
   247         tablesql = y2sql.eschema2sql(cnx.repo.system_source.dbhelper,
   248                                      eschema, prefix=SQL_PREFIX)
   248                                      eschema, prefix=SQL_PREFIX)
   249         for sql in tablesql.split(';'):
   249         for sql in tablesql.split(';'):
   250             if sql.strip():
   250             if sql.strip():
   251                 session.system_sql(sql)
   251                 cnx.system_sql(sql)
   252         # add meta relations
   252         # add meta relations
   253         gmap = group_mapping(session)
   253         gmap = group_mapping(cnx)
   254         cmap = ss.cstrtype_mapping(session)
   254         cmap = ss.cstrtype_mapping(cnx)
   255         for rtype in (META_RTYPES - VIRTUAL_RTYPES):
   255         for rtype in (META_RTYPES - VIRTUAL_RTYPES):
   256             try:
   256             try:
   257                 rschema = schema[rtype]
   257                 rschema = schema[rtype]
   258             except KeyError:
   258             except KeyError:
   259                 self.critical('rtype %s was not handled at cwetype creation time', rtype)
   259                 self.critical('rtype %s was not handled at cwetype creation time', rtype)
   268             except KeyError:
   268             except KeyError:
   269                 # this combo does not exist because this is not a universal META_RTYPE
   269                 # this combo does not exist because this is not a universal META_RTYPE
   270                 continue
   270                 continue
   271             rdef.subject = _MockEntity(eid=entity.eid)
   271             rdef.subject = _MockEntity(eid=entity.eid)
   272             mock = _MockEntity(eid=None)
   272             mock = _MockEntity(eid=None)
   273             ss.execschemarql(session.execute, mock, ss.rdef2rql(rdef, cmap, gmap))
   273             ss.execschemarql(cnx.execute, mock, ss.rdef2rql(rdef, cmap, gmap))
   274 
   274 
   275     def revertprecommit_event(self):
   275     def revertprecommit_event(self):
   276         # revert changes on in memory schema
   276         # revert changes on in memory schema
   277         self.session.vreg.schema.del_entity_type(self.entity.name)
   277         self.cnx.vreg.schema.del_entity_type(self.entity.name)
   278         # revert changes on database
   278         # revert changes on database
   279         self.session.system_sql('DROP TABLE %s%s' % (SQL_PREFIX, self.entity.name))
   279         self.cnx.system_sql('DROP TABLE %s%s' % (SQL_PREFIX, self.entity.name))
   280 
   280 
   281 
   281 
   282 class CWETypeRenameOp(MemSchemaOperation):
   282 class CWETypeRenameOp(MemSchemaOperation):
   283     """this operation updates physical storage accordingly"""
   283     """this operation updates physical storage accordingly"""
   284     oldname = newname = None # make pylint happy
   284     oldname = newname = None # make pylint happy
   285 
   285 
   286     def rename(self, oldname, newname):
   286     def rename(self, oldname, newname):
   287         self.session.vreg.schema.rename_entity_type(oldname, newname)
   287         self.cnx.vreg.schema.rename_entity_type(oldname, newname)
   288         # we need sql to operate physical changes on the system database
   288         # we need sql to operate physical changes on the system database
   289         sqlexec = self.session.system_sql
   289         sqlexec = self.cnx.system_sql
   290         dbhelper = self.session.repo.system_source.dbhelper
   290         dbhelper = self.cnx.repo.system_source.dbhelper
   291         sql = dbhelper.sql_rename_table(SQL_PREFIX+oldname,
   291         sql = dbhelper.sql_rename_table(SQL_PREFIX+oldname,
   292                                         SQL_PREFIX+newname)
   292                                         SQL_PREFIX+newname)
   293         sqlexec(sql)
   293         sqlexec(sql)
   294         self.info('renamed table %s to %s', oldname, newname)
   294         self.info('renamed table %s to %s', oldname, newname)
   295         sqlexec('UPDATE entities SET type=%(newname)s WHERE type=%(oldname)s',
   295         sqlexec('UPDATE entities SET type=%(newname)s WHERE type=%(oldname)s',
   296                 {'newname': newname, 'oldname': oldname})
   296                 {'newname': newname, 'oldname': oldname})
   297         for eid, (etype, extid, auri) in self.session.repo._type_source_cache.items():
   297         for eid, (etype, extid, auri) in self.cnx.repo._type_source_cache.items():
   298             if etype == oldname:
   298             if etype == oldname:
   299                 self.session.repo._type_source_cache[eid] = (newname, extid, auri)
   299                 self.cnx.repo._type_source_cache[eid] = (newname, extid, auri)
   300         # XXX transaction records
   300         # XXX transaction records
   301 
   301 
   302     def precommit_event(self):
   302     def precommit_event(self):
   303         self.rename(self.oldname, self.newname)
   303         self.rename(self.oldname, self.newname)
   304 
   304 
   313 
   313 
   314     def precommit_event(self):
   314     def precommit_event(self):
   315         rschema = self.rschema
   315         rschema = self.rschema
   316         if rschema.final:
   316         if rschema.final:
   317             return # watched changes to final relation type are unexpected
   317             return # watched changes to final relation type are unexpected
   318         session = self.session
   318         cnx = self.cnx
   319         if 'fulltext_container' in self.values:
   319         if 'fulltext_container' in self.values:
   320             op = UpdateFTIndexOp.get_instance(session)
   320             op = UpdateFTIndexOp.get_instance(cnx)
   321             for subjtype, objtype in rschema.rdefs:
   321             for subjtype, objtype in rschema.rdefs:
   322                 op.add_data(subjtype)
   322                 op.add_data(subjtype)
   323                 op.add_data(objtype)
   323                 op.add_data(objtype)
   324         # update the in-memory schema first
   324         # update the in-memory schema first
   325         self.oldvalues = dict( (attr, getattr(rschema, attr)) for attr in self.values)
   325         self.oldvalues = dict( (attr, getattr(rschema, attr)) for attr in self.values)
   330         inlined = self.values['inlined']
   330         inlined = self.values['inlined']
   331         # check in-lining is possible when inlined
   331         # check in-lining is possible when inlined
   332         if inlined:
   332         if inlined:
   333             self.entity.check_inlined_allowed()
   333             self.entity.check_inlined_allowed()
   334         # inlined changed, make necessary physical changes!
   334         # inlined changed, make necessary physical changes!
   335         sqlexec = self.session.system_sql
   335         sqlexec = self.cnx.system_sql
   336         rtype = rschema.type
   336         rtype = rschema.type
   337         eidcolumn = SQL_PREFIX + 'eid'
   337         eidcolumn = SQL_PREFIX + 'eid'
   338         if not inlined:
   338         if not inlined:
   339             # need to create the relation if it has not been already done by
   339             # need to create the relation if it has not been already done by
   340             # another event of the same transaction
   340             # another event of the same transaction
   341             if not rschema.type in session.transaction_data.get('createdtables', ()):
   341             if not rschema.type in cnx.transaction_data.get('createdtables', ()):
   342                 tablesql = y2sql.rschema2sql(rschema)
   342                 tablesql = y2sql.rschema2sql(rschema)
   343                 # create the necessary table
   343                 # create the necessary table
   344                 for sql in tablesql.split(';'):
   344                 for sql in tablesql.split(';'):
   345                     if sql.strip():
   345                     if sql.strip():
   346                         sqlexec(sql)
   346                         sqlexec(sql)
   347                 session.transaction_data.setdefault('createdtables', []).append(
   347                 cnx.transaction_data.setdefault('createdtables', []).append(
   348                     rschema.type)
   348                     rschema.type)
   349             # copy existant data
   349             # copy existant data
   350             column = SQL_PREFIX + rtype
   350             column = SQL_PREFIX + rtype
   351             for etype in rschema.subjects():
   351             for etype in rschema.subjects():
   352                 table = SQL_PREFIX + str(etype)
   352                 table = SQL_PREFIX + str(etype)
   353                 sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL'
   353                 sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL'
   354                         % (rtype, eidcolumn, column, table, column))
   354                         % (rtype, eidcolumn, column, table, column))
   355             # drop existant columns
   355             # drop existant columns
   356             #if session.repo.system_source.dbhelper.alter_column_support:
   356             #if cnx.repo.system_source.dbhelper.alter_column_support:
   357             for etype in rschema.subjects():
   357             for etype in rschema.subjects():
   358                 DropColumn(session, table=SQL_PREFIX + str(etype),
   358                 DropColumn(cnx, table=SQL_PREFIX + str(etype),
   359                            column=SQL_PREFIX + rtype)
   359                            column=SQL_PREFIX + rtype)
   360         else:
   360         else:
   361             for etype in rschema.subjects():
   361             for etype in rschema.subjects():
   362                 try:
   362                 try:
   363                     add_inline_relation_column(session, str(etype), rtype)
   363                     add_inline_relation_column(cnx, str(etype), rtype)
   364                 except Exception as ex:
   364                 except Exception as ex:
   365                     # the column probably already exists. this occurs when the
   365                     # the column probably already exists. this occurs when the
   366                     # entity's type has just been added or if the column has not
   366                     # entity's type has just been added or if the column has not
   367                     # been previously dropped (eg sqlite)
   367                     # been previously dropped (eg sqlite)
   368                     self.error('error while altering table %s: %s', etype, ex)
   368                     self.error('error while altering table %s: %s', etype, ex)
   380                 if args:
   380                 if args:
   381                     column = SQL_PREFIX + rtype
   381                     column = SQL_PREFIX + rtype
   382                     cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s'
   382                     cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s'
   383                                        % (table, column, eidcolumn), args)
   383                                        % (table, column, eidcolumn), args)
   384                 # drop existant table
   384                 # drop existant table
   385                 DropRelationTable(session, rtype)
   385                 DropRelationTable(cnx, rtype)
   386 
   386 
   387     def revertprecommit_event(self):
   387     def revertprecommit_event(self):
   388         # revert changes on in memory schema
   388         # revert changes on in memory schema
   389         self.rschema.__dict__.update(self.oldvalues)
   389         self.rschema.__dict__.update(self.oldvalues)
   390         # XXX revert changes on database
   390         # XXX revert changes on database
   405         entity = self.entity
   405         entity = self.entity
   406         fromentity = entity.stype
   406         fromentity = entity.stype
   407         rdefdef = self.rdefdef = ybo.RelationDefinition(
   407         rdefdef = self.rdefdef = ybo.RelationDefinition(
   408             str(fromentity.name), entity.rtype.name, str(entity.otype.name),
   408             str(fromentity.name), entity.rtype.name, str(entity.otype.name),
   409             description=entity.description, cardinality=entity.cardinality,
   409             description=entity.description, cardinality=entity.cardinality,
   410             constraints=get_constraints(self.session, entity),
   410             constraints=get_constraints(self.cnx, entity),
   411             order=entity.ordernum, eid=entity.eid, **kwargs)
   411             order=entity.ordernum, eid=entity.eid, **kwargs)
   412         self.session.vreg.schema.add_relation_def(rdefdef)
   412         self.cnx.vreg.schema.add_relation_def(rdefdef)
   413         self.session.execute('SET X ordernum Y+1 '
   413         self.cnx.execute('SET X ordernum Y+1 '
   414                              'WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, '
   414                              'WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, '
   415                              'X ordernum >= %(order)s, NOT X eid %(x)s',
   415                              'X ordernum >= %(order)s, NOT X eid %(x)s',
   416                              {'x': entity.eid, 'se': fromentity.eid,
   416                              {'x': entity.eid, 'se': fromentity.eid,
   417                               'order': entity.ordernum or 0})
   417                               'order': entity.ordernum or 0})
   418         return rdefdef
   418         return rdefdef
   419 
   419 
   420     def precommit_event(self):
   420     def precommit_event(self):
   421         session = self.session
   421         cnx = self.cnx
   422         entity = self.entity
   422         entity = self.entity
   423         # entity.defaultval is a Binary or None, but we need a correctly typed
   423         # entity.defaultval is a Binary or None, but we need a correctly typed
   424         # value
   424         # value
   425         default = entity.defaultval
   425         default = entity.defaultval
   426         if default is not None:
   426         if default is not None:
   430                  'fulltextindexed': entity.fulltextindexed,
   430                  'fulltextindexed': entity.fulltextindexed,
   431                  'internationalizable': entity.internationalizable}
   431                  'internationalizable': entity.internationalizable}
   432         # update the in-memory schema first
   432         # update the in-memory schema first
   433         rdefdef = self.init_rdef(**props)
   433         rdefdef = self.init_rdef(**props)
   434         # then make necessary changes to the system source database
   434         # then make necessary changes to the system source database
   435         syssource = session.repo.system_source
   435         syssource = cnx.repo.system_source
   436         attrtype = y2sql.type_from_constraints(
   436         attrtype = y2sql.type_from_constraints(
   437             syssource.dbhelper, rdefdef.object, rdefdef.constraints)
   437             syssource.dbhelper, rdefdef.object, rdefdef.constraints)
   438         # XXX should be moved somehow into lgdb: sqlite doesn't support to
   438         # XXX should be moved somehow into lgdb: sqlite doesn't support to
   439         # add a new column with UNIQUE, it should be added after the ALTER TABLE
   439         # add a new column with UNIQUE, it should be added after the ALTER TABLE
   440         # using ADD INDEX
   440         # using ADD INDEX
   446         # added some str() wrapping query since some backend (eg psycopg) don't
   446         # added some str() wrapping query since some backend (eg psycopg) don't
   447         # allow unicode queries
   447         # allow unicode queries
   448         table = SQL_PREFIX + rdefdef.subject
   448         table = SQL_PREFIX + rdefdef.subject
   449         column = SQL_PREFIX + rdefdef.name
   449         column = SQL_PREFIX + rdefdef.name
   450         try:
   450         try:
   451             session.system_sql(str('ALTER TABLE %s ADD %s %s'
   451             cnx.system_sql(str('ALTER TABLE %s ADD %s %s'
   452                                    % (table, column, attrtype)),
   452                                    % (table, column, attrtype)),
   453                                rollback_on_failure=False)
   453                                rollback_on_failure=False)
   454             self.info('added column %s to table %s', table, column)
   454             self.info('added column %s to table %s', table, column)
   455         except Exception as ex:
   455         except Exception as ex:
   456             # the column probably already exists. this occurs when
   456             # the column probably already exists. this occurs when
   457             # the entity's type has just been added or if the column
   457             # the entity's type has just been added or if the column
   458             # has not been previously dropped
   458             # has not been previously dropped
   459             self.error('error while altering table %s: %s', table, ex)
   459             self.error('error while altering table %s: %s', table, ex)
   460         if extra_unique_index or entity.indexed:
   460         if extra_unique_index or entity.indexed:
   461             try:
   461             try:
   462                 syssource.create_index(session, table, column,
   462                 syssource.create_index(cnx, table, column,
   463                                       unique=extra_unique_index)
   463                                       unique=extra_unique_index)
   464             except Exception as ex:
   464             except Exception as ex:
   465                 self.error('error while creating index for %s.%s: %s',
   465                 self.error('error while creating index for %s.%s: %s',
   466                            table, column, ex)
   466                            table, column, ex)
   467         # final relations are not infered, propagate
   467         # final relations are not infered, propagate
   468         schema = session.vreg.schema
   468         schema = cnx.vreg.schema
   469         try:
   469         try:
   470             eschema = schema.eschema(rdefdef.subject)
   470             eschema = schema.eschema(rdefdef.subject)
   471         except KeyError:
   471         except KeyError:
   472             return # entity type currently being added
   472             return # entity type currently being added
   473         # propagate attribute to children classes
   473         # propagate attribute to children classes
   474         rschema = schema.rschema(rdefdef.name)
   474         rschema = schema.rschema(rdefdef.name)
   475         # if relation type has been inserted in the same transaction, its final
   475         # if relation type has been inserted in the same transaction, its final
   476         # attribute is still set to False, so we've to ensure it's False
   476         # attribute is still set to False, so we've to ensure it's False
   477         rschema.final = True
   477         rschema.final = True
   478         insert_rdef_on_subclasses(session, eschema, rschema, rdefdef, props)
   478         insert_rdef_on_subclasses(cnx, eschema, rschema, rdefdef, props)
   479         # update existing entities with the default value of newly added attribute
   479         # update existing entities with the default value of newly added attribute
   480         if default is not None:
   480         if default is not None:
   481             default = convert_default_value(self.rdefdef, default)
   481             default = convert_default_value(self.rdefdef, default)
   482             session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
   482             cnx.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
   483                                {'default': default})
   483                                {'default': default})
   484 
   484 
   485     def revertprecommit_event(self):
   485     def revertprecommit_event(self):
   486         # revert changes on in memory schema
   486         # revert changes on in memory schema
   487         if getattr(self, 'rdefdef', None) is None:
   487         if getattr(self, 'rdefdef', None) is None:
   488             return
   488             return
   489         self.session.vreg.schema.del_relation_def(
   489         self.cnx.vreg.schema.del_relation_def(
   490             self.rdefdef.subject, self.rdefdef.name, self.rdefdef.object)
   490             self.rdefdef.subject, self.rdefdef.name, self.rdefdef.object)
   491         # XXX revert changes on database
   491         # XXX revert changes on database
   492 
   492 
   493 
   493 
   494 class CWRelationAddOp(CWAttributeAddOp):
   494 class CWRelationAddOp(CWAttributeAddOp):
   503     constraints are handled by specific hooks
   503     constraints are handled by specific hooks
   504     """
   504     """
   505     entity = None # make pylint happy
   505     entity = None # make pylint happy
   506 
   506 
   507     def precommit_event(self):
   507     def precommit_event(self):
   508         session = self.session
   508         cnx = self.cnx
   509         entity = self.entity
   509         entity = self.entity
   510         # update the in-memory schema first
   510         # update the in-memory schema first
   511         rdefdef = self.init_rdef(composite=entity.composite)
   511         rdefdef = self.init_rdef(composite=entity.composite)
   512         # then make necessary changes to the system source database
   512         # then make necessary changes to the system source database
   513         schema = session.vreg.schema
   513         schema = cnx.vreg.schema
   514         rtype = rdefdef.name
   514         rtype = rdefdef.name
   515         rschema = schema.rschema(rtype)
   515         rschema = schema.rschema(rtype)
   516         # this have to be done before permissions setting
   516         # this have to be done before permissions setting
   517         if rschema.inlined:
   517         if rschema.inlined:
   518             # need to add a column if the relation is inlined and if this is the
   518             # need to add a column if the relation is inlined and if this is the
   519             # first occurence of "Subject relation Something" whatever Something
   519             # first occurence of "Subject relation Something" whatever Something
   520             if len(rschema.objects(rdefdef.subject)) == 1:
   520             if len(rschema.objects(rdefdef.subject)) == 1:
   521                 add_inline_relation_column(session, rdefdef.subject, rtype)
   521                 add_inline_relation_column(cnx, rdefdef.subject, rtype)
   522             eschema = schema[rdefdef.subject]
   522             eschema = schema[rdefdef.subject]
   523             insert_rdef_on_subclasses(session, eschema, rschema, rdefdef,
   523             insert_rdef_on_subclasses(cnx, eschema, rschema, rdefdef,
   524                                       {'composite': entity.composite})
   524                                       {'composite': entity.composite})
   525         else:
   525         else:
   526             if rschema.symmetric:
   526             if rschema.symmetric:
   527                 # for symmetric relations, rdefs will store relation definitions
   527                 # for symmetric relations, rdefs will store relation definitions
   528                 # in both ways (i.e. (subj -> obj) and (obj -> subj))
   528                 # in both ways (i.e. (subj -> obj) and (obj -> subj))
   531                 relation_already_defined = len(rschema.rdefs) > 1
   531                 relation_already_defined = len(rschema.rdefs) > 1
   532             # need to create the relation if no relation definition in the
   532             # need to create the relation if no relation definition in the
   533             # schema and if it has not been added during other event of the same
   533             # schema and if it has not been added during other event of the same
   534             # transaction
   534             # transaction
   535             if not (relation_already_defined or
   535             if not (relation_already_defined or
   536                     rtype in session.transaction_data.get('createdtables', ())):
   536                     rtype in cnx.transaction_data.get('createdtables', ())):
   537                 rschema = schema.rschema(rtype)
   537                 rschema = schema.rschema(rtype)
   538                 # create the necessary table
   538                 # create the necessary table
   539                 for sql in y2sql.rschema2sql(rschema).split(';'):
   539                 for sql in y2sql.rschema2sql(rschema).split(';'):
   540                     if sql.strip():
   540                     if sql.strip():
   541                         session.system_sql(sql)
   541                         cnx.system_sql(sql)
   542                 session.transaction_data.setdefault('createdtables', []).append(
   542                 cnx.transaction_data.setdefault('createdtables', []).append(
   543                     rtype)
   543                     rtype)
   544 
   544 
   545     # XXX revertprecommit_event
   545     # XXX revertprecommit_event
   546 
   546 
   547 
   547 
   548 class RDefDelOp(MemSchemaOperation):
   548 class RDefDelOp(MemSchemaOperation):
   549     """an actual relation has been removed"""
   549     """an actual relation has been removed"""
   550     rdef = None # make pylint happy
   550     rdef = None # make pylint happy
   551 
   551 
   552     def precommit_event(self):
   552     def precommit_event(self):
   553         session = self.session
   553         cnx = self.cnx
   554         rdef = self.rdef
   554         rdef = self.rdef
   555         rschema = rdef.rtype
   555         rschema = rdef.rtype
   556         # make necessary changes to the system source database first
   556         # make necessary changes to the system source database first
   557         rdeftype = rschema.final and 'CWAttribute' or 'CWRelation'
   557         rdeftype = rschema.final and 'CWAttribute' or 'CWRelation'
   558         execute = session.execute
   558         execute = cnx.execute
   559         rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,'
   559         rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,'
   560                        'R eid %%(x)s' % rdeftype, {'x': rschema.eid})
   560                        'R eid %%(x)s' % rdeftype, {'x': rschema.eid})
   561         lastrel = rset[0][0] == 0
   561         lastrel = rset[0][0] == 0
   562         # we have to update physical schema systematically for final and inlined
   562         # we have to update physical schema systematically for final and inlined
   563         # relations, but only if it's the last instance for this relation type
   563         # relations, but only if it's the last instance for this relation type
   565         if (rschema.final or rschema.inlined):
   565         if (rschema.final or rschema.inlined):
   566             rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
   566             rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
   567                            'R eid %%(r)s, X from_entity E, E eid %%(e)s'
   567                            'R eid %%(r)s, X from_entity E, E eid %%(e)s'
   568                            % rdeftype,
   568                            % rdeftype,
   569                            {'r': rschema.eid, 'e': rdef.subject.eid})
   569                            {'r': rschema.eid, 'e': rdef.subject.eid})
   570             if rset[0][0] == 0 and not session.deleted_in_transaction(rdef.subject.eid):
   570             if rset[0][0] == 0 and not cnx.deleted_in_transaction(rdef.subject.eid):
   571                 ptypes = session.transaction_data.setdefault('pendingrtypes', set())
   571                 ptypes = cnx.transaction_data.setdefault('pendingrtypes', set())
   572                 ptypes.add(rschema.type)
   572                 ptypes.add(rschema.type)
   573                 DropColumn(session, table=SQL_PREFIX + str(rdef.subject),
   573                 DropColumn(cnx, table=SQL_PREFIX + str(rdef.subject),
   574                            column=SQL_PREFIX + str(rschema))
   574                            column=SQL_PREFIX + str(rschema))
   575         elif lastrel:
   575         elif lastrel:
   576             DropRelationTable(session, str(rschema))
   576             DropRelationTable(cnx, str(rschema))
   577         # then update the in-memory schema
   577         # then update the in-memory schema
   578         if rdef.subject not in ETYPE_NAME_MAP and rdef.object not in ETYPE_NAME_MAP:
   578         if rdef.subject not in ETYPE_NAME_MAP and rdef.object not in ETYPE_NAME_MAP:
   579             rschema.del_relation_def(rdef.subject, rdef.object)
   579             rschema.del_relation_def(rdef.subject, rdef.object)
   580         # if this is the last relation definition of this type, drop associated
   580         # if this is the last relation definition of this type, drop associated
   581         # relation type
   581         # relation type
   582         if lastrel and not session.deleted_in_transaction(rschema.eid):
   582         if lastrel and not cnx.deleted_in_transaction(rschema.eid):
   583             execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rschema.eid})
   583             execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rschema.eid})
   584 
   584 
   585     def revertprecommit_event(self):
   585     def revertprecommit_event(self):
   586         # revert changes on in memory schema
   586         # revert changes on in memory schema
   587         #
   587         #
   588         # Note: add_relation_def takes a RelationDefinition, not a
   588         # Note: add_relation_def takes a RelationDefinition, not a
   589         # RelationDefinitionSchema, needs to fake it
   589         # RelationDefinitionSchema, needs to fake it
   590         rdef = self.rdef
   590         rdef = self.rdef
   591         rdef.name = str(rdef.rtype)
   591         rdef.name = str(rdef.rtype)
   592         if rdef.subject not in ETYPE_NAME_MAP and rdef.object not in ETYPE_NAME_MAP:
   592         if rdef.subject not in ETYPE_NAME_MAP and rdef.object not in ETYPE_NAME_MAP:
   593             self.session.vreg.schema.add_relation_def(rdef)
   593             self.cnx.vreg.schema.add_relation_def(rdef)
   594 
   594 
   595 
   595 
   596 
   596 
   597 class RDefUpdateOp(MemSchemaOperation):
   597 class RDefUpdateOp(MemSchemaOperation):
   598     """actually update some properties of a relation definition"""
   598     """actually update some properties of a relation definition"""
   599     rschema = rdefkey = values = None # make pylint happy
   599     rschema = rdefkey = values = None # make pylint happy
   600     rdef = oldvalues = None
   600     rdef = oldvalues = None
   601     indexed_changed = null_allowed_changed = False
   601     indexed_changed = null_allowed_changed = False
   602 
   602 
   603     def precommit_event(self):
   603     def precommit_event(self):
   604         session = self.session
   604         cnx = self.cnx
   605         rdef = self.rdef = self.rschema.rdefs[self.rdefkey]
   605         rdef = self.rdef = self.rschema.rdefs[self.rdefkey]
   606         # update the in-memory schema first
   606         # update the in-memory schema first
   607         self.oldvalues = dict( (attr, getattr(rdef, attr)) for attr in self.values)
   607         self.oldvalues = dict( (attr, getattr(rdef, attr)) for attr in self.values)
   608         rdef.update(self.values)
   608         rdef.update(self.values)
   609         # then make necessary changes to the system source database
   609         # then make necessary changes to the system source database
   610         syssource = session.repo.system_source
   610         syssource = cnx.repo.system_source
   611         if 'indexed' in self.values:
   611         if 'indexed' in self.values:
   612             syssource.update_rdef_indexed(session, rdef)
   612             syssource.update_rdef_indexed(cnx, rdef)
   613             self.indexed_changed = True
   613             self.indexed_changed = True
   614         if 'cardinality' in self.values and (rdef.rtype.final or
   614         if 'cardinality' in self.values and (rdef.rtype.final or
   615                                              rdef.rtype.inlined) \
   615                                              rdef.rtype.inlined) \
   616               and self.values['cardinality'][0] != self.oldvalues['cardinality'][0]:
   616               and self.values['cardinality'][0] != self.oldvalues['cardinality'][0]:
   617             syssource.update_rdef_null_allowed(self.session, rdef)
   617             syssource.update_rdef_null_allowed(self.cnx, rdef)
   618             self.null_allowed_changed = True
   618             self.null_allowed_changed = True
   619         if 'fulltextindexed' in self.values:
   619         if 'fulltextindexed' in self.values:
   620             UpdateFTIndexOp.get_instance(session).add_data(rdef.subject)
   620             UpdateFTIndexOp.get_instance(cnx).add_data(rdef.subject)
   621 
   621 
   622     def revertprecommit_event(self):
   622     def revertprecommit_event(self):
   623         if self.rdef is None:
   623         if self.rdef is None:
   624             return
   624             return
   625         # revert changes on in memory schema
   625         # revert changes on in memory schema
   626         self.rdef.update(self.oldvalues)
   626         self.rdef.update(self.oldvalues)
   627         # revert changes on database
   627         # revert changes on database
   628         syssource = self.session.repo.system_source
   628         syssource = self.cnx.repo.system_source
   629         if self.indexed_changed:
   629         if self.indexed_changed:
   630             syssource.update_rdef_indexed(self.session, self.rdef)
   630             syssource.update_rdef_indexed(self.cnx, self.rdef)
   631         if self.null_allowed_changed:
   631         if self.null_allowed_changed:
   632             syssource.update_rdef_null_allowed(self.session, self.rdef)
   632             syssource.update_rdef_null_allowed(self.cnx, self.rdef)
   633 
   633 
   634 
   634 
   635 def _set_modifiable_constraints(rdef):
   635 def _set_modifiable_constraints(rdef):
   636     # for proper in-place modification of in-memory schema: if rdef.constraints
   636     # for proper in-place modification of in-memory schema: if rdef.constraints
   637     # is already a list, reuse it (we're updating multiple constraints of the
   637     # is already a list, reuse it (we're updating multiple constraints of the
   644     """actually remove a constraint of a relation definition"""
   644     """actually remove a constraint of a relation definition"""
   645     rdef = oldcstr = newcstr = None # make pylint happy
   645     rdef = oldcstr = newcstr = None # make pylint happy
   646     size_cstr_changed = unique_changed = False
   646     size_cstr_changed = unique_changed = False
   647 
   647 
   648     def precommit_event(self):
   648     def precommit_event(self):
   649         session = self.session
   649         cnx = self.cnx
   650         rdef = self.rdef
   650         rdef = self.rdef
   651         # in-place modification of in-memory schema first
   651         # in-place modification of in-memory schema first
   652         _set_modifiable_constraints(rdef)
   652         _set_modifiable_constraints(rdef)
   653         rdef.constraints.remove(self.oldcstr)
   653         rdef.constraints.remove(self.oldcstr)
   654         # then update database: alter the physical schema on size/unique
   654         # then update database: alter the physical schema on size/unique
   655         # constraint changes
   655         # constraint changes
   656         syssource = session.repo.system_source
   656         syssource = cnx.repo.system_source
   657         cstrtype = self.oldcstr.type()
   657         cstrtype = self.oldcstr.type()
   658         if cstrtype == 'SizeConstraint':
   658         if cstrtype == 'SizeConstraint':
   659             syssource.update_rdef_column(session, rdef)
   659             syssource.update_rdef_column(cnx, rdef)
   660             self.size_cstr_changed = True
   660             self.size_cstr_changed = True
   661         elif cstrtype == 'UniqueConstraint':
   661         elif cstrtype == 'UniqueConstraint':
   662             syssource.update_rdef_unique(session, rdef)
   662             syssource.update_rdef_unique(cnx, rdef)
   663             self.unique_changed = True
   663             self.unique_changed = True
   664 
   664 
   665     def revertprecommit_event(self):
   665     def revertprecommit_event(self):
   666         # revert changes on in memory schema
   666         # revert changes on in memory schema
   667         if self.newcstr is not None:
   667         if self.newcstr is not None:
   668             self.rdef.constraints.remove(self.newcstr)
   668             self.rdef.constraints.remove(self.newcstr)
   669         if self.oldcstr is not None:
   669         if self.oldcstr is not None:
   670             self.rdef.constraints.append(self.oldcstr)
   670             self.rdef.constraints.append(self.oldcstr)
   671         # revert changes on database
   671         # revert changes on database
   672         syssource = self.session.repo.system_source
   672         syssource = self.cnx.repo.system_source
   673         if self.size_cstr_changed:
   673         if self.size_cstr_changed:
   674             syssource.update_rdef_column(self.session, self.rdef)
   674             syssource.update_rdef_column(self.cnx, self.rdef)
   675         if self.unique_changed:
   675         if self.unique_changed:
   676             syssource.update_rdef_unique(self.session, self.rdef)
   676             syssource.update_rdef_unique(self.cnx, self.rdef)
   677 
   677 
   678 
   678 
   679 class CWConstraintAddOp(CWConstraintDelOp):
   679 class CWConstraintAddOp(CWConstraintDelOp):
   680     """actually update constraint of a relation definition"""
   680     """actually update constraint of a relation definition"""
   681     entity = None # make pylint happy
   681     entity = None # make pylint happy
   682 
   682 
   683     def precommit_event(self):
   683     def precommit_event(self):
   684         session = self.session
   684         cnx = self.cnx
   685         rdefentity = self.entity.reverse_constrained_by[0]
   685         rdefentity = self.entity.reverse_constrained_by[0]
   686         # when the relation is added in the same transaction, the constraint
   686         # when the relation is added in the same transaction, the constraint
   687         # object is created by the operation adding the attribute or relation,
   687         # object is created by the operation adding the attribute or relation,
   688         # so there is nothing to do here
   688         # so there is nothing to do here
   689         if session.added_in_transaction(rdefentity.eid):
   689         if cnx.added_in_transaction(rdefentity.eid):
   690             return
   690             return
   691         rdef = self.rdef = session.vreg.schema.schema_by_eid(rdefentity.eid)
   691         rdef = self.rdef = cnx.vreg.schema.schema_by_eid(rdefentity.eid)
   692         cstrtype = self.entity.type
   692         cstrtype = self.entity.type
   693         oldcstr = self.oldcstr = rdef.constraint_by_type(cstrtype)
   693         oldcstr = self.oldcstr = rdef.constraint_by_type(cstrtype)
   694         newcstr = self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
   694         newcstr = self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
   695         # in-place modification of in-memory schema first
   695         # in-place modification of in-memory schema first
   696         _set_modifiable_constraints(rdef)
   696         _set_modifiable_constraints(rdef)
   698         if oldcstr is not None:
   698         if oldcstr is not None:
   699             rdef.constraints.remove(oldcstr)
   699             rdef.constraints.remove(oldcstr)
   700         rdef.constraints.append(newcstr)
   700         rdef.constraints.append(newcstr)
   701         # then update database: alter the physical schema on size/unique
   701         # then update database: alter the physical schema on size/unique
   702         # constraint changes
   702         # constraint changes
   703         syssource = session.repo.system_source
   703         syssource = cnx.repo.system_source
   704         if cstrtype == 'SizeConstraint' and (oldcstr is None or
   704         if cstrtype == 'SizeConstraint' and (oldcstr is None or
   705                                              oldcstr.max != newcstr.max):
   705                                              oldcstr.max != newcstr.max):
   706             syssource.update_rdef_column(session, rdef)
   706             syssource.update_rdef_column(cnx, rdef)
   707             self.size_cstr_changed = True
   707             self.size_cstr_changed = True
   708         elif cstrtype == 'UniqueConstraint' and oldcstr is None:
   708         elif cstrtype == 'UniqueConstraint' and oldcstr is None:
   709             syssource.update_rdef_unique(session, rdef)
   709             syssource.update_rdef_unique(cnx, rdef)
   710             self.unique_changed = True
   710             self.unique_changed = True
   711 
   711 
   712 
   712 
   713 class CWUniqueTogetherConstraintAddOp(MemSchemaOperation):
   713 class CWUniqueTogetherConstraintAddOp(MemSchemaOperation):
   714     entity = None # make pylint happy
   714     entity = None # make pylint happy
   715 
   715 
   716     def precommit_event(self):
   716     def precommit_event(self):
   717         session = self.session
   717         cnx = self.cnx
   718         prefix = SQL_PREFIX
   718         prefix = SQL_PREFIX
   719         entity = self.entity
   719         entity = self.entity
   720         table = '%s%s' % (prefix, entity.constraint_of[0].name)
   720         table = '%s%s' % (prefix, entity.constraint_of[0].name)
   721         cols = ['%s%s' % (prefix, r.name) for r in entity.relations]
   721         cols = ['%s%s' % (prefix, r.name) for r in entity.relations]
   722         dbhelper = session.repo.system_source.dbhelper
   722         dbhelper = cnx.repo.system_source.dbhelper
   723         sqls = dbhelper.sqls_create_multicol_unique_index(table, cols, entity.name)
   723         sqls = dbhelper.sqls_create_multicol_unique_index(table, cols, entity.name)
   724         for sql in sqls:
   724         for sql in sqls:
   725             session.system_sql(sql)
   725             cnx.system_sql(sql)
   726 
   726 
   727     def postcommit_event(self):
   727     def postcommit_event(self):
   728         entity = self.entity
   728         entity = self.entity
   729         eschema = self.session.vreg.schema.schema_by_eid(entity.constraint_of[0].eid)
   729         eschema = self.cnx.vreg.schema.schema_by_eid(entity.constraint_of[0].eid)
   730         attrs = [r.name for r in entity.relations]
   730         attrs = [r.name for r in entity.relations]
   731         eschema._unique_together.append(attrs)
   731         eschema._unique_together.append(attrs)
   732 
   732 
   733 
   733 
   734 class CWUniqueTogetherConstraintDelOp(MemSchemaOperation):
   734 class CWUniqueTogetherConstraintDelOp(MemSchemaOperation):
   735     entity = cstrname = None # for pylint
   735     entity = cstrname = None # for pylint
   736     cols = () # for pylint
   736     cols = () # for pylint
   737 
   737 
   738     def precommit_event(self):
   738     def precommit_event(self):
   739         session = self.session
   739         cnx = self.cnx
   740         prefix = SQL_PREFIX
   740         prefix = SQL_PREFIX
   741         table = '%s%s' % (prefix, self.entity.type)
   741         table = '%s%s' % (prefix, self.entity.type)
   742         dbhelper = session.repo.system_source.dbhelper
   742         dbhelper = cnx.repo.system_source.dbhelper
   743         cols = ['%s%s' % (prefix, c) for c in self.cols]
   743         cols = ['%s%s' % (prefix, c) for c in self.cols]
   744         sqls = dbhelper.sqls_drop_multicol_unique_index(table, cols, self.cstrname)
   744         sqls = dbhelper.sqls_drop_multicol_unique_index(table, cols, self.cstrname)
   745         for sql in sqls:
   745         for sql in sqls:
   746             session.system_sql(sql)
   746             cnx.system_sql(sql)
   747 
   747 
   748     def postcommit_event(self):
   748     def postcommit_event(self):
   749         eschema = self.session.vreg.schema.schema_by_eid(self.entity.eid)
   749         eschema = self.cnx.vreg.schema.schema_by_eid(self.entity.eid)
   750         cols = set(self.cols)
   750         cols = set(self.cols)
   751         unique_together = [ut for ut in eschema._unique_together
   751         unique_together = [ut for ut in eschema._unique_together
   752                            if set(ut) != cols]
   752                            if set(ut) != cols]
   753         eschema._unique_together = unique_together
   753         eschema._unique_together = unique_together
   754 
   754 
   759     """actually remove the entity type from the instance's schema"""
   759     """actually remove the entity type from the instance's schema"""
   760     etype = None # make pylint happy
   760     etype = None # make pylint happy
   761 
   761 
   762     def postcommit_event(self):
   762     def postcommit_event(self):
   763         # del_entity_type also removes entity's relations
   763         # del_entity_type also removes entity's relations
   764         self.session.vreg.schema.del_entity_type(self.etype)
   764         self.cnx.vreg.schema.del_entity_type(self.etype)
   765 
   765 
   766 
   766 
   767 class MemSchemaCWRTypeAdd(MemSchemaOperation):
   767 class MemSchemaCWRTypeAdd(MemSchemaOperation):
   768     """actually add the relation type to the instance's schema"""
   768     """actually add the relation type to the instance's schema"""
   769     rtypedef = None # make pylint happy
   769     rtypedef = None # make pylint happy
   770 
   770 
   771     def precommit_event(self):
   771     def precommit_event(self):
   772         self.session.vreg.schema.add_relation_type(self.rtypedef)
   772         self.cnx.vreg.schema.add_relation_type(self.rtypedef)
   773 
   773 
   774     def revertprecommit_event(self):
   774     def revertprecommit_event(self):
   775         self.session.vreg.schema.del_relation_type(self.rtypedef.name)
   775         self.cnx.vreg.schema.del_relation_type(self.rtypedef.name)
   776 
   776 
   777 
   777 
   778 class MemSchemaCWRTypeDel(MemSchemaOperation):
   778 class MemSchemaCWRTypeDel(MemSchemaOperation):
   779     """actually remove the relation type from the instance's schema"""
   779     """actually remove the relation type from the instance's schema"""
   780     rtype = None # make pylint happy
   780     rtype = None # make pylint happy
   781 
   781 
   782     def postcommit_event(self):
   782     def postcommit_event(self):
   783         try:
   783         try:
   784             self.session.vreg.schema.del_relation_type(self.rtype)
   784             self.cnx.vreg.schema.del_relation_type(self.rtype)
   785         except KeyError:
   785         except KeyError:
   786             # s/o entity type have already been deleted
   786             # s/o entity type have already been deleted
   787             pass
   787             pass
   788 
   788 
   789 
   789 
   793     eid = action = group_eid = expr = None # make pylint happy
   793     eid = action = group_eid = expr = None # make pylint happy
   794 
   794 
   795     def precommit_event(self):
   795     def precommit_event(self):
   796         """the observed connections.cnxset has been commited"""
   796         """the observed connections.cnxset has been commited"""
   797         try:
   797         try:
   798             erschema = self.session.vreg.schema.schema_by_eid(self.eid)
   798             erschema = self.cnx.vreg.schema.schema_by_eid(self.eid)
   799         except KeyError:
   799         except KeyError:
   800             # duh, schema not found, log error and skip operation
   800             # duh, schema not found, log error and skip operation
   801             self.warning('no schema for %s', self.eid)
   801             self.warning('no schema for %s', self.eid)
   802             return
   802             return
   803         perms = list(erschema.action_permissions(self.action))
   803         perms = list(erschema.action_permissions(self.action))
   804         if self.group_eid is not None:
   804         if self.group_eid is not None:
   805             perm = self.session.entity_from_eid(self.group_eid).name
   805             perm = self.cnx.entity_from_eid(self.group_eid).name
   806         else:
   806         else:
   807             perm = erschema.rql_expression(self.expr)
   807             perm = erschema.rql_expression(self.expr)
   808         try:
   808         try:
   809             perms.index(perm)
   809             perms.index(perm)
   810             self.warning('%s already in permissions for %s on %s',
   810             self.warning('%s already in permissions for %s on %s',
   822     """
   822     """
   823 
   823 
   824     def precommit_event(self):
   824     def precommit_event(self):
   825         """the observed connections set has been commited"""
   825         """the observed connections set has been commited"""
   826         try:
   826         try:
   827             erschema = self.session.vreg.schema.schema_by_eid(self.eid)
   827             erschema = self.cnx.vreg.schema.schema_by_eid(self.eid)
   828         except KeyError:
   828         except KeyError:
   829             # duh, schema not found, log error and skip operation
   829             # duh, schema not found, log error and skip operation
   830             self.warning('no schema for %s', self.eid)
   830             self.warning('no schema for %s', self.eid)
   831             return
   831             return
   832         if isinstance(erschema, RelationSchema): # XXX 3.6 migration
   832         if isinstance(erschema, RelationSchema): # XXX 3.6 migration
   834         if isinstance(erschema, RelationDefinitionSchema) and \
   834         if isinstance(erschema, RelationDefinitionSchema) and \
   835                self.action in ('delete', 'add'): # XXX 3.6.1 migration
   835                self.action in ('delete', 'add'): # XXX 3.6.1 migration
   836             return
   836             return
   837         perms = list(erschema.action_permissions(self.action))
   837         perms = list(erschema.action_permissions(self.action))
   838         if self.group_eid is not None:
   838         if self.group_eid is not None:
   839             perm = self.session.entity_from_eid(self.group_eid).name
   839             perm = self.cnx.entity_from_eid(self.group_eid).name
   840         else:
   840         else:
   841             perm = erschema.rql_expression(self.expr)
   841             perm = erschema.rql_expression(self.expr)
   842         try:
   842         try:
   843             perms.remove(perm)
   843             perms.remove(perm)
   844             erschema.set_action_permissions(self.action, perms)
   844             erschema.set_action_permissions(self.action, perms)
   851 
   851 
   852 class MemSchemaSpecializesAdd(MemSchemaOperation):
   852 class MemSchemaSpecializesAdd(MemSchemaOperation):
   853     etypeeid = parentetypeeid = None # make pylint happy
   853     etypeeid = parentetypeeid = None # make pylint happy
   854 
   854 
   855     def precommit_event(self):
   855     def precommit_event(self):
   856         eschema = self.session.vreg.schema.schema_by_eid(self.etypeeid)
   856         eschema = self.cnx.vreg.schema.schema_by_eid(self.etypeeid)
   857         parenteschema = self.session.vreg.schema.schema_by_eid(self.parentetypeeid)
   857         parenteschema = self.cnx.vreg.schema.schema_by_eid(self.parentetypeeid)
   858         eschema._specialized_type = parenteschema.type
   858         eschema._specialized_type = parenteschema.type
   859         parenteschema._specialized_by.append(eschema.type)
   859         parenteschema._specialized_by.append(eschema.type)
   860 
   860 
   861     # XXX revertprecommit_event
   861     # XXX revertprecommit_event
   862 
   862 
   864 class MemSchemaSpecializesDel(MemSchemaOperation):
   864 class MemSchemaSpecializesDel(MemSchemaOperation):
   865     etypeeid = parentetypeeid = None # make pylint happy
   865     etypeeid = parentetypeeid = None # make pylint happy
   866 
   866 
   867     def precommit_event(self):
   867     def precommit_event(self):
   868         try:
   868         try:
   869             eschema = self.session.vreg.schema.schema_by_eid(self.etypeeid)
   869             eschema = self.cnx.vreg.schema.schema_by_eid(self.etypeeid)
   870             parenteschema = self.session.vreg.schema.schema_by_eid(self.parentetypeeid)
   870             parenteschema = self.cnx.vreg.schema.schema_by_eid(self.parentetypeeid)
   871         except KeyError:
   871         except KeyError:
   872             # etype removed, nothing to do
   872             # etype removed, nothing to do
   873             return
   873             return
   874         eschema._specialized_type = None
   874         eschema._specialized_type = None
   875         parenteschema._specialized_by.remove(eschema.type)
   875         parenteschema._specialized_by.remove(eschema.type)
  1024     __regid__ = 'syncdelrelationtype'
  1024     __regid__ = 'syncdelrelationtype'
  1025     __select__ = SyncSchemaHook.__select__ & hook.match_rtype('relation_type')
  1025     __select__ = SyncSchemaHook.__select__ & hook.match_rtype('relation_type')
  1026     events = ('after_delete_relation',)
  1026     events = ('after_delete_relation',)
  1027 
  1027 
  1028     def __call__(self):
  1028     def __call__(self):
  1029         session = self._cw
  1029         cnx = self._cw
  1030         try:
  1030         try:
  1031             rdef = session.vreg.schema.schema_by_eid(self.eidfrom)
  1031             rdef = cnx.vreg.schema.schema_by_eid(self.eidfrom)
  1032         except KeyError:
  1032         except KeyError:
  1033             self.critical('cant get schema rdef associated to %s', self.eidfrom)
  1033             self.critical('cant get schema rdef associated to %s', self.eidfrom)
  1034             return
  1034             return
  1035         subjschema, rschema, objschema = rdef.as_triple()
  1035         subjschema, rschema, objschema = rdef.as_triple()
  1036         pendingrdefs = session.transaction_data.setdefault('pendingrdefs', set())
  1036         pendingrdefs = cnx.transaction_data.setdefault('pendingrdefs', set())
  1037         # first delete existing relation if necessary
  1037         # first delete existing relation if necessary
  1038         if rschema.final:
  1038         if rschema.final:
  1039             rdeftype = 'CWAttribute'
  1039             rdeftype = 'CWAttribute'
  1040             pendingrdefs.add((subjschema, rschema))
  1040             pendingrdefs.add((subjschema, rschema))
  1041         else:
  1041         else:
  1042             rdeftype = 'CWRelation'
  1042             rdeftype = 'CWRelation'
  1043             pendingrdefs.add((subjschema, rschema, objschema))
  1043             pendingrdefs.add((subjschema, rschema, objschema))
  1044             if not (session.deleted_in_transaction(subjschema.eid) or
  1044             if not (cnx.deleted_in_transaction(subjschema.eid) or
  1045                     session.deleted_in_transaction(objschema.eid)):
  1045                     cnx.deleted_in_transaction(objschema.eid)):
  1046                 session.execute('DELETE X %s Y WHERE X is %s, Y is %s'
  1046                 cnx.execute('DELETE X %s Y WHERE X is %s, Y is %s'
  1047                                 % (rschema, subjschema, objschema))
  1047                                 % (rschema, subjschema, objschema))
  1048         RDefDelOp(session, rdef=rdef)
  1048         RDefDelOp(cnx, rdef=rdef)
  1049 
  1049 
  1050 
  1050 
  1051 # CWAttribute / CWRelation hooks ###############################################
  1051 # CWAttribute / CWRelation hooks ###############################################
  1052 
  1052 
  1053 class AfterAddCWAttributeHook(SyncSchemaHook):
  1053 class AfterAddCWAttributeHook(SyncSchemaHook):
  1217     We wait after the commit to as the schema in memory is only updated after
  1217     We wait after the commit to as the schema in memory is only updated after
  1218     the commit.
  1218     the commit.
  1219     """
  1219     """
  1220 
  1220 
  1221     def postcommit_event(self):
  1221     def postcommit_event(self):
  1222         session = self.session
  1222         cnx = self.cnx
  1223         source = session.repo.system_source
  1223         source = cnx.repo.system_source
  1224         schema = session.repo.vreg.schema
  1224         schema = cnx.repo.vreg.schema
  1225         to_reindex = self.get_data()
  1225         to_reindex = self.get_data()
  1226         self.info('%i etypes need full text indexed reindexation',
  1226         self.info('%i etypes need full text indexed reindexation',
  1227                   len(to_reindex))
  1227                   len(to_reindex))
  1228         for etype in to_reindex:
  1228         for etype in to_reindex:
  1229             rset = session.execute('Any X WHERE X is %s' % etype)
  1229             rset = cnx.execute('Any X WHERE X is %s' % etype)
  1230             self.info('Reindexing full text index for %i entity of type %s',
  1230             self.info('Reindexing full text index for %i entity of type %s',
  1231                       len(rset), etype)
  1231                       len(rset), etype)
  1232             still_fti = list(schema[etype].indexable_attributes())
  1232             still_fti = list(schema[etype].indexable_attributes())
  1233             for entity in rset.entities():
  1233             for entity in rset.entities():
  1234                 source.fti_unindex_entities(session, [entity])
  1234                 source.fti_unindex_entities(cnx, [entity])
  1235                 for container in entity.cw_adapt_to('IFTIndexable').fti_containers():
  1235                 for container in entity.cw_adapt_to('IFTIndexable').fti_containers():
  1236                     if still_fti or container is not entity:
  1236                     if still_fti or container is not entity:
  1237                         source.fti_unindex_entities(session, [container])
  1237                         source.fti_unindex_entities(cnx, [container])
  1238                         source.fti_index_entities(session, [container])
  1238                         source.fti_index_entities(cnx, [container])
  1239         if to_reindex:
  1239         if to_reindex:
  1240             # Transaction has already been committed
  1240             # Transaction has already been committed
  1241             session.cnxset.commit()
  1241             cnx.cnxset.commit()
  1242 
  1242 
  1243 
  1243 
  1244 
  1244 
  1245 
  1245 
  1246 # specializes synchronization hooks ############################################
  1246 # specializes synchronization hooks ############################################