server/schemahooks.py
branchtls-sprint
changeset 1802 d628defebc17
parent 1398 5fe84a5f7035
child 1977 606923dff11b
child 1981 e6eed4324357
equal deleted inserted replaced
1801:672acc730ce5 1802:d628defebc17
    19 from cubicweb.server import schemaserial as ss
    19 from cubicweb.server import schemaserial as ss
    20 from cubicweb.server.sqlutils import SQL_PREFIX
    20 from cubicweb.server.sqlutils import SQL_PREFIX
    21 from cubicweb.server.pool import Operation, SingleLastOperation, PreCommitOperation
    21 from cubicweb.server.pool import Operation, SingleLastOperation, PreCommitOperation
    22 from cubicweb.server.hookhelper import (entity_attr, entity_name,
    22 from cubicweb.server.hookhelper import (entity_attr, entity_name,
    23                                      check_internal_entity)
    23                                      check_internal_entity)
    24     
    24 
    25 # core entity and relation types which can't be removed
    25 # core entity and relation types which can't be removed
    26 CORE_ETYPES = list(BASE_TYPES) + ['CWEType', 'CWRType', 'CWUser', 'CWGroup',
    26 CORE_ETYPES = list(BASE_TYPES) + ['CWEType', 'CWRType', 'CWUser', 'CWGroup',
    27                                   'CWConstraint', 'CWAttribute', 'CWRelation']
    27                                   'CWConstraint', 'CWAttribute', 'CWRelation']
    28 CORE_RTYPES = ['eid', 'creation_date', 'modification_date',
    28 CORE_RTYPES = ['eid', 'creation_date', 'modification_date',
    29                'login', 'upassword', 'name',
    29                'login', 'upassword', 'name',
    50     try:
    50     try:
    51         session.system_sql(str('ALTER TABLE %s ADD COLUMN %s integer'
    51         session.system_sql(str('ALTER TABLE %s ADD COLUMN %s integer'
    52                                % (table, column)))
    52                                % (table, column)))
    53         session.info('added column %s to table %s', column, table)
    53         session.info('added column %s to table %s', column, table)
    54     except:
    54     except:
    55         # silent exception here, if this error has not been raised because the 
    55         # silent exception here, if this error has not been raised because the
    56         # column already exists, index creation will fail anyway
    56         # column already exists, index creation will fail anyway
    57         session.exception('error while adding column %s to table %s',
    57         session.exception('error while adding column %s to table %s',
    58                           table, column)
    58                           table, column)
    59     # create index before alter table which may expectingly fail during test
    59     # create index before alter table which may expectingly fail during test
    60     # (sqlite) while index creation should never fail (test for index existence
    60     # (sqlite) while index creation should never fail (test for index existence
    72         # once Operation.__init__ has been called, event may be triggered, so
    72         # once Operation.__init__ has been called, event may be triggered, so
    73         # do this last !
    73         # do this last !
    74         Operation.__init__(self, session, **kwargs)
    74         Operation.__init__(self, session, **kwargs)
    75         # every schema operation is triggering a schema update
    75         # every schema operation is triggering a schema update
    76         UpdateSchemaOp(session)
    76         UpdateSchemaOp(session)
    77         
    77 
    78 class EarlySchemaOperation(SchemaOperation):
    78 class EarlySchemaOperation(SchemaOperation):
    79     def insert_index(self):
    79     def insert_index(self):
    80         """schema operation which are inserted at the begining of the queue
    80         """schema operation which are inserted at the begining of the queue
    81         (typically to add/remove entity or relation types)
    81         (typically to add/remove entity or relation types)
    82         """
    82         """
    83         i = -1
    83         i = -1
    84         for i, op in enumerate(self.session.pending_operations):
    84         for i, op in enumerate(self.session.pending_operations):
    85             if not isinstance(op, EarlySchemaOperation):
    85             if not isinstance(op, EarlySchemaOperation):
    86                 return i
    86                 return i
    87         return i + 1
    87         return i + 1
    88     
    88 
    89 class UpdateSchemaOp(SingleLastOperation):
    89 class UpdateSchemaOp(SingleLastOperation):
    90     """the update schema operation:
    90     """the update schema operation:
    91 
    91 
    92     special operation which should be called once and after all other schema
    92     special operation which should be called once and after all other schema
    93     operations. It will trigger internal structures rebuilding to consider
    93     operations. It will trigger internal structures rebuilding to consider
    94     schema changes
    94     schema changes
    95     """
    95     """
    96     
    96 
    97     def __init__(self, session):
    97     def __init__(self, session):
    98         self.repo = session.repo
    98         self.repo = session.repo
    99         SingleLastOperation.__init__(self, session)
    99         SingleLastOperation.__init__(self, session)
   100         
   100 
   101     def commit_event(self):
   101     def commit_event(self):
   102         self.repo.set_schema(self.repo.schema)
   102         self.repo.set_schema(self.repo.schema)
   103 
   103 
   104         
   104 
   105 class DropTableOp(PreCommitOperation):
   105 class DropTableOp(PreCommitOperation):
   106     """actually remove a database from the application's schema"""
   106     """actually remove a database from the application's schema"""
   107     table = None # make pylint happy
   107     table = None # make pylint happy
   108     def precommit_event(self):
   108     def precommit_event(self):
   109         dropped = self.session.query_data('droppedtables',
   109         dropped = self.session.query_data('droppedtables',
   111         if self.table in dropped:
   111         if self.table in dropped:
   112             return # already processed
   112             return # already processed
   113         dropped.add(self.table)
   113         dropped.add(self.table)
   114         self.session.system_sql('DROP TABLE %s' % self.table)
   114         self.session.system_sql('DROP TABLE %s' % self.table)
   115         self.info('dropped table %s', self.table)
   115         self.info('dropped table %s', self.table)
   116         
   116 
   117 class DropColumnOp(PreCommitOperation):
   117 class DropColumnOp(PreCommitOperation):
   118     """actually remove the attribut's column from entity table in the system
   118     """actually remove the attribut's column from entity table in the system
   119     database
   119     database
   120     """
   120     """
   121     table = column = None # make pylint happy
   121     table = column = None # make pylint happy
   128                                % (table, column))
   128                                % (table, column))
   129             self.info('dropped column %s from table %s', column, table)
   129             self.info('dropped column %s from table %s', column, table)
   130         except Exception, ex:
   130         except Exception, ex:
   131             # not supported by sqlite for instance
   131             # not supported by sqlite for instance
   132             self.error('error while altering table %s: %s', table, ex)
   132             self.error('error while altering table %s: %s', table, ex)
   133             
   133 
   134 
   134 
   135 # deletion ####################################################################
   135 # deletion ####################################################################
   136 
   136 
   137 class DeleteCWETypeOp(SchemaOperation):
   137 class DeleteCWETypeOp(SchemaOperation):
   138     """actually remove the entity type from the application's schema"""    
   138     """actually remove the entity type from the application's schema"""
   139     def commit_event(self):
   139     def commit_event(self):
   140         try:
   140         try:
   141             # del_entity_type also removes entity's relations
   141             # del_entity_type also removes entity's relations
   142             self.schema.del_entity_type(self.kobj)
   142             self.schema.del_entity_type(self.kobj)
   143         except KeyError:
   143         except KeyError:
   160 def after_del_eetype(session, eid):
   160 def after_del_eetype(session, eid):
   161     # workflow cleanup
   161     # workflow cleanup
   162     session.execute('DELETE State X WHERE NOT X state_of Y')
   162     session.execute('DELETE State X WHERE NOT X state_of Y')
   163     session.execute('DELETE Transition X WHERE NOT X transition_of Y')
   163     session.execute('DELETE Transition X WHERE NOT X transition_of Y')
   164 
   164 
   165         
   165 
   166 class DeleteCWRTypeOp(SchemaOperation):
   166 class DeleteCWRTypeOp(SchemaOperation):
   167     """actually remove the relation type from the application's schema"""    
   167     """actually remove the relation type from the application's schema"""
   168     def commit_event(self):
   168     def commit_event(self):
   169         try:
   169         try:
   170             self.schema.del_relation_type(self.kobj)
   170             self.schema.del_relation_type(self.kobj)
   171         except KeyError:
   171         except KeyError:
   172             # s/o entity type have already been deleted
   172             # s/o entity type have already been deleted
   184                     {'x': eid})
   184                     {'x': eid})
   185     session.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',
   185     session.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',
   186                     {'x': eid})
   186                     {'x': eid})
   187     DeleteCWRTypeOp(session, name)
   187     DeleteCWRTypeOp(session, name)
   188 
   188 
   189     
   189 
   190 class DelErdefOp(SchemaOperation):
   190 class DelErdefOp(SchemaOperation):
   191     """actually remove the relation definition from the application's schema"""
   191     """actually remove the relation definition from the application's schema"""
   192     def commit_event(self):
   192     def commit_event(self):
   193         subjtype, rtype, objtype = self.kobj
   193         subjtype, rtype, objtype = self.kobj
   194         try:
   194         try:
   195             self.schema.del_relation_def(subjtype, rtype, objtype)
   195             self.schema.del_relation_def(subjtype, rtype, objtype)
   196         except KeyError:
   196         except KeyError:
   197             # relation type may have been already deleted
   197             # relation type may have been already deleted
   198             pass
   198             pass
   199         
   199 
   200 def after_del_relation_type(session, rdefeid, rtype, rteid):
   200 def after_del_relation_type(session, rdefeid, rtype, rteid):
   201     """before deleting a CWAttribute or CWRelation entity:
   201     """before deleting a CWAttribute or CWRelation entity:
   202     * if this is a final or inlined relation definition, instantiate an
   202     * if this is a final or inlined relation definition, instantiate an
   203       operation to drop necessary column, else if this is the last instance
   203       operation to drop necessary column, else if this is the last instance
   204       of a non final relation, instantiate an operation to drop necessary
   204       of a non final relation, instantiate an operation to drop necessary
   221                    'R eid %%(x)s' % rdeftype, {'x': rteid})
   221                    'R eid %%(x)s' % rdeftype, {'x': rteid})
   222     lastrel = rset[0][0] == 0
   222     lastrel = rset[0][0] == 0
   223     # we have to update physical schema systematically for final and inlined
   223     # we have to update physical schema systematically for final and inlined
   224     # relations, but only if it's the last instance for this relation type
   224     # relations, but only if it's the last instance for this relation type
   225     # for other relations
   225     # for other relations
   226     
   226 
   227     if (rschema.is_final() or rschema.inlined):
   227     if (rschema.is_final() or rschema.inlined):
   228         rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
   228         rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
   229                        'R eid %%(x)s, X from_entity E, E name %%(name)s'
   229                        'R eid %%(x)s, X from_entity E, E name %%(name)s'
   230                        % rdeftype, {'x': rteid, 'name': str(subjschema)})
   230                        % rdeftype, {'x': rteid, 'name': str(subjschema)})
   231         if rset[0][0] == 0 and not subjschema.eid in pendings:
   231         if rset[0][0] == 0 and not subjschema.eid in pendings:
   236     # if this is the last instance, drop associated relation type
   236     # if this is the last instance, drop associated relation type
   237     if lastrel and not rteid in pendings:
   237     if lastrel and not rteid in pendings:
   238         execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x')
   238         execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x')
   239     DelErdefOp(session, (subjschema, rschema, objschema))
   239     DelErdefOp(session, (subjschema, rschema, objschema))
   240 
   240 
   241         
   241 
   242 # addition ####################################################################
   242 # addition ####################################################################
   243 
   243 
   244 class AddCWETypeOp(EarlySchemaOperation):
   244 class AddCWETypeOp(EarlySchemaOperation):
   245     """actually add the entity type to the application's schema"""    
   245     """actually add the entity type to the application's schema"""
   246     eid = None # make pylint happy
   246     eid = None # make pylint happy
   247     def commit_event(self):
   247     def commit_event(self):
   248         eschema = self.schema.add_entity_type(self.kobj)
   248         eschema = self.schema.add_entity_type(self.kobj)
   249         eschema.eid = self.eid
   249         eschema.eid = self.eid
   250         
   250 
   251 def before_add_eetype(session, entity):
   251 def before_add_eetype(session, entity):
   252     """before adding a CWEType entity:
   252     """before adding a CWEType entity:
   253     * check that we are not using an existing entity type,
   253     * check that we are not using an existing entity type,
   254     """
   254     """
   255     name = entity['name']
   255     name = entity['name']
   302     for rql, kwargs in relrqls:
   302     for rql, kwargs in relrqls:
   303         session.execute(rql, kwargs)
   303         session.execute(rql, kwargs)
   304 
   304 
   305 
   305 
   306 class AddCWRTypeOp(EarlySchemaOperation):
   306 class AddCWRTypeOp(EarlySchemaOperation):
   307     """actually add the relation type to the application's schema"""    
   307     """actually add the relation type to the application's schema"""
   308     eid = None # make pylint happy
   308     eid = None # make pylint happy
   309     def commit_event(self):
   309     def commit_event(self):
   310         rschema = self.schema.add_relation_type(self.kobj)
   310         rschema = self.schema.add_relation_type(self.kobj)
   311         rschema.set_default_groups()
   311         rschema.set_default_groups()
   312         rschema.eid = self.eid
   312         rschema.eid = self.eid
   313         
   313 
   314 def before_add_ertype(session, entity):
   314 def before_add_ertype(session, entity):
   315     """before adding a CWRType entity:
   315     """before adding a CWRType entity:
   316     * check that we are not using an existing relation type,
   316     * check that we are not using an existing relation type,
   317     * register an operation to add the relation type to the application's
   317     * register an operation to add the relation type to the application's
   318       schema on commit
   318       schema on commit
   319       
   319 
   320     We don't know yeat this point if a table is necessary
   320     We don't know yeat this point if a table is necessary
   321     """
   321     """
   322     name = entity['name']
   322     name = entity['name']
   323     if name in session.repo.schema.relations():
   323     if name in session.repo.schema.relations():
   324         raise RepositoryError('a relation type %s already exists' % name)
   324         raise RepositoryError('a relation type %s already exists' % name)
   325     
   325 
   326 def after_add_ertype(session, entity):
   326 def after_add_ertype(session, entity):
   327     """after a CWRType entity has been added:
   327     """after a CWRType entity has been added:
   328     * register an operation to add the relation type to the application's
   328     * register an operation to add the relation type to the application's
   329       schema on commit
   329       schema on commit
   330     We don't know yeat this point if a table is necessary
   330     We don't know yeat this point if a table is necessary
   338 
   338 
   339 
   339 
   340 class AddErdefOp(EarlySchemaOperation):
   340 class AddErdefOp(EarlySchemaOperation):
   341     """actually add the attribute relation definition to the application's
   341     """actually add the attribute relation definition to the application's
   342     schema
   342     schema
   343     """    
   343     """
   344     def commit_event(self):
   344     def commit_event(self):
   345         self.schema.add_relation_def(self.kobj)
   345         self.schema.add_relation_def(self.kobj)
   346 
   346 
   347 TYPE_CONVERTER = {
   347 TYPE_CONVERTER = {
   348     'Boolean': bool,
   348     'Boolean': bool,
   349     'Int': int,
   349     'Int': int,
   350     'Float': float,
   350     'Float': float,
   351     'Password': str,
   351     'Password': str,
   352     'String': unicode,
   352     'String': unicode,
   353     'Date' : unicode, 
   353     'Date' : unicode,
   354     'Datetime' : unicode,
   354     'Datetime' : unicode,
   355     'Time' : unicode,
   355     'Time' : unicode,
   356     }
   356     }
   357 
   357 
   358 
   358 
   360     """an attribute relation (CWAttribute) has been added:
   360     """an attribute relation (CWAttribute) has been added:
   361     * add the necessary column
   361     * add the necessary column
   362     * set default on this column if any and possible
   362     * set default on this column if any and possible
   363     * register an operation to add the relation definition to the
   363     * register an operation to add the relation definition to the
   364       application's schema on commit
   364       application's schema on commit
   365       
   365 
   366     constraints are handled by specific hooks
   366     constraints are handled by specific hooks
   367     """
   367     """
   368     entity = None # make pylint happy
   368     entity = None # make pylint happy
   369     def precommit_event(self):
   369     def precommit_event(self):
   370         session = self.session
   370         session = self.session
   455     entity = None # make pylint happy
   455     entity = None # make pylint happy
   456     def precommit_event(self):
   456     def precommit_event(self):
   457         session = self.session
   457         session = self.session
   458         entity = self.entity
   458         entity = self.entity
   459         fromentity = entity.from_entity[0]
   459         fromentity = entity.from_entity[0]
   460         relationtype = entity.relation_type[0] 
   460         relationtype = entity.relation_type[0]
   461         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',
   461         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',
   462                         {'x': entity.eid, 'se': fromentity.eid, 'order': entity.ordernum or 0})
   462                         {'x': entity.eid, 'se': fromentity.eid, 'order': entity.ordernum or 0})
   463         subj, rtype = str(fromentity.name), str(relationtype.name)
   463         subj, rtype = str(fromentity.name), str(relationtype.name)
   464         obj = str(entity.to_entity[0].name)
   464         obj = str(entity.to_entity[0].name)
   465         card = entity.get('cardinality')
   465         card = entity.get('cardinality')
   506                 # create the necessary table
   506                 # create the necessary table
   507                 for sql in tablesql.split(';'):
   507                 for sql in tablesql.split(';'):
   508                     if sql.strip():
   508                     if sql.strip():
   509                         self.session.system_sql(sql)
   509                         self.session.system_sql(sql)
   510                 session.add_query_data('createdtables', rtype)
   510                 session.add_query_data('createdtables', rtype)
   511                 
   511 
   512 def after_add_enfrdef(session, entity):
   512 def after_add_enfrdef(session, entity):
   513     AddCWRelationPreCommitOp(session, entity=entity)
   513     AddCWRelationPreCommitOp(session, entity=entity)
   514 
   514 
   515 
   515 
   516 # update ######################################################################
   516 # update ######################################################################
   554         self.info('renamed table %s to %s', self.oldname, self.newname)
   554         self.info('renamed table %s to %s', self.oldname, self.newname)
   555         sqlexec('UPDATE entities SET type=%s WHERE type=%s',
   555         sqlexec('UPDATE entities SET type=%s WHERE type=%s',
   556                 (self.newname, self.oldname))
   556                 (self.newname, self.oldname))
   557         sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s',
   557         sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s',
   558                 (self.newname, self.oldname))
   558                 (self.newname, self.oldname))
   559         
   559 
   560     def commit_event(self):
   560     def commit_event(self):
   561         self.session.repo.schema.rename_entity_type(self.oldname, self.newname)
   561         self.session.repo.schema.rename_entity_type(self.oldname, self.newname)
   562 
   562 
   563 
   563 
   564 class UpdateRdefOp(SchemaOperation):
   564 class UpdateRdefOp(SchemaOperation):
   573             column = SQL_PREFIX + rtype
   573             column = SQL_PREFIX + rtype
   574             if self.values['indexed']:
   574             if self.values['indexed']:
   575                 sysource.create_index(self.session, table, column)
   575                 sysource.create_index(self.session, table, column)
   576             else:
   576             else:
   577                 sysource.drop_index(self.session, table, column)
   577                 sysource.drop_index(self.session, table, column)
   578                 
   578 
   579     def commit_event(self):
   579     def commit_event(self):
   580         # structure should be clean, not need to remove entity's relations
   580         # structure should be clean, not need to remove entity's relations
   581         # at this point
   581         # at this point
   582         self.rschema._rproperties[self.kobj].update(self.values)
   582         self.rschema._rproperties[self.kobj].update(self.values)
   583 
   583 
   584     
   584 
   585 def after_update_erdef(session, entity):
   585 def after_update_erdef(session, entity):
   586     desttype = entity.to_entity[0].name
   586     desttype = entity.to_entity[0].name
   587     rschema = session.repo.schema[entity.relation_type[0].name]
   587     rschema = session.repo.schema[entity.relation_type[0].name]
   588     newvalues = {}
   588     newvalues = {}
   589     for prop in rschema.rproperty_defs(desttype):
   589     for prop in rschema.rproperty_defs(desttype):
   598         UpdateRdefOp(session, (subjtype, desttype), rschema=rschema,
   598         UpdateRdefOp(session, (subjtype, desttype), rschema=rschema,
   599                      values=newvalues)
   599                      values=newvalues)
   600 
   600 
   601 
   601 
   602 class UpdateRtypeOp(SchemaOperation):
   602 class UpdateRtypeOp(SchemaOperation):
   603     """actually update some properties of a relation definition"""    
   603     """actually update some properties of a relation definition"""
   604     rschema = values = entity = None # make pylint happy
   604     rschema = values = entity = None # make pylint happy
   605 
   605 
   606     def precommit_event(self):
   606     def precommit_event(self):
   607         session = self.session
   607         session = self.session
   608         rschema = self.rschema
   608         rschema = self.rschema
   637                 DropColumnOp(session, table=SQL_PREFIX + str(etype),
   637                 DropColumnOp(session, table=SQL_PREFIX + str(etype),
   638                              column=SQL_PREFIX + rtype)
   638                              column=SQL_PREFIX + rtype)
   639         else:
   639         else:
   640             for etype in rschema.subjects():
   640             for etype in rschema.subjects():
   641                 try:
   641                 try:
   642                     add_inline_relation_column(session, str(etype), rtype)                    
   642                     add_inline_relation_column(session, str(etype), rtype)
   643                 except Exception, ex:
   643                 except Exception, ex:
   644                     # the column probably already exists. this occurs when
   644                     # the column probably already exists. this occurs when
   645                     # the entity's type has just been added or if the column
   645                     # the entity's type has just been added or if the column
   646                     # has not been previously dropped
   646                     # has not been previously dropped
   647                     self.error('error while altering table %s: %s', etype, ex)
   647                     self.error('error while altering table %s: %s', etype, ex)
   648                 # copy existant data. 
   648                 # copy existant data.
   649                 # XXX don't use, it's not supported by sqlite (at least at when i tried it)
   649                 # XXX don't use, it's not supported by sqlite (at least at when i tried it)
   650                 #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to '
   650                 #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to '
   651                 #        'FROM %(rtype)s_relation '
   651                 #        'FROM %(rtype)s_relation '
   652                 #        'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from'
   652                 #        'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from'
   653                 #        % locals())
   653                 #        % locals())
   665 
   665 
   666     def commit_event(self):
   666     def commit_event(self):
   667         # structure should be clean, not need to remove entity's relations
   667         # structure should be clean, not need to remove entity's relations
   668         # at this point
   668         # at this point
   669         self.rschema.__dict__.update(self.values)
   669         self.rschema.__dict__.update(self.values)
   670     
   670 
   671 def after_update_ertype(session, entity):
   671 def after_update_ertype(session, entity):
   672     rschema = session.repo.schema.rschema(entity.name)
   672     rschema = session.repo.schema.rschema(entity.name)
   673     newvalues = {}
   673     newvalues = {}
   674     for prop in ('meta', 'symetric', 'inlined'):
   674     for prop in ('meta', 'symetric', 'inlined'):
   675         if prop in entity:
   675         if prop in entity:
   682 from cubicweb.schema import CONSTRAINTS
   682 from cubicweb.schema import CONSTRAINTS
   683 
   683 
   684 class ConstraintOp(SchemaOperation):
   684 class ConstraintOp(SchemaOperation):
   685     """actually update constraint of a relation definition"""
   685     """actually update constraint of a relation definition"""
   686     entity = None # make pylint happy
   686     entity = None # make pylint happy
   687     
   687 
   688     def prepare_constraints(self, rtype, subjtype, objtype):
   688     def prepare_constraints(self, rtype, subjtype, objtype):
   689         constraints = rtype.rproperty(subjtype, objtype, 'constraints')
   689         constraints = rtype.rproperty(subjtype, objtype, 'constraints')
   690         self.constraints = list(constraints)
   690         self.constraints = list(constraints)
   691         rtype.set_rproperty(subjtype, objtype, 'constraints', self.constraints)
   691         rtype.set_rproperty(subjtype, objtype, 'constraints', self.constraints)
   692         return self.constraints
   692         return self.constraints
   693     
   693 
   694     def precommit_event(self):
   694     def precommit_event(self):
   695         rdef = self.entity.reverse_constrained_by[0]
   695         rdef = self.entity.reverse_constrained_by[0]
   696         session = self.session
   696         session = self.session
   697         # when the relation is added in the same transaction, the constraint object
   697         # when the relation is added in the same transaction, the constraint object
   698         # is created by AddEN?FRDefPreCommitOp, there is nothing to do here
   698         # is created by AddEN?FRDefPreCommitOp, there is nothing to do here
   699         if rdef.eid in session.query_data('neweids', ()):
   699         if rdef.eid in session.query_data('neweids', ()):
   700             self.cancelled = True
   700             self.cancelled = True
   701             return 
   701             return
   702         self.cancelled = False
   702         self.cancelled = False
   703         schema = session.repo.schema
   703         schema = session.repo.schema
   704         subjtype, rtype, objtype = schema.schema_by_eid(rdef.eid)
   704         subjtype, rtype, objtype = schema.schema_by_eid(rdef.eid)
   705         self.prepare_constraints(rtype, subjtype, objtype)
   705         self.prepare_constraints(rtype, subjtype, objtype)
   706         cstrtype = self.entity.type
   706         cstrtype = self.entity.type
   721                 # not supported by sqlite for instance
   721                 # not supported by sqlite for instance
   722                 self.error('error while altering table %s: %s', table, ex)
   722                 self.error('error while altering table %s: %s', table, ex)
   723         elif cstrtype == 'UniqueConstraint':
   723         elif cstrtype == 'UniqueConstraint':
   724             session.pool.source('system').create_index(
   724             session.pool.source('system').create_index(
   725                 self.session, table, column, unique=True)
   725                 self.session, table, column, unique=True)
   726         
   726 
   727     def commit_event(self):
   727     def commit_event(self):
   728         if self.cancelled:
   728         if self.cancelled:
   729             return
   729             return
   730         # in-place removing
   730         # in-place removing
   731         if not self.cstr is None:
   731         if not self.cstr is None:
   741 
   741 
   742 
   742 
   743 class DelConstraintOp(ConstraintOp):
   743 class DelConstraintOp(ConstraintOp):
   744     """actually remove a constraint of a relation definition"""
   744     """actually remove a constraint of a relation definition"""
   745     rtype = subjtype = objtype = None # make pylint happy
   745     rtype = subjtype = objtype = None # make pylint happy
   746     
   746 
   747     def precommit_event(self):
   747     def precommit_event(self):
   748         self.prepare_constraints(self.rtype, self.subjtype, self.objtype)
   748         self.prepare_constraints(self.rtype, self.subjtype, self.objtype)
   749         cstrtype = self.cstr.type()
   749         cstrtype = self.cstr.type()
   750         table = SQL_PREFIX + str(self.subjtype)
   750         table = SQL_PREFIX + str(self.subjtype)
   751         column = SQL_PREFIX + str(self.rtype)
   751         column = SQL_PREFIX + str(self.rtype)
   752         # alter the physical schema on size/unique constraint changes
   752         # alter the physical schema on size/unique constraint changes
   753         if cstrtype == 'SizeConstraint':
   753         if cstrtype == 'SizeConstraint':
   754             try:
   754             try:
   755                 self.session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE TEXT'
   755                 self.session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE TEXT'
   756                                         % (table, column))
   756                                         % (table, column))
   757                 self.info('altered column %s of table %s: now TEXT', 
   757                 self.info('altered column %s of table %s: now TEXT',
   758                           column, table)
   758                           column, table)
   759             except Exception, ex:
   759             except Exception, ex:
   760                 # not supported by sqlite for instance
   760                 # not supported by sqlite for instance
   761                 self.error('error while altering table %s: %s', table, ex)
   761                 self.error('error while altering table %s: %s', table, ex)
   762         elif cstrtype == 'UniqueConstraint':
   762         elif cstrtype == 'UniqueConstraint':
   763             self.session.pool.source('system').drop_index(
   763             self.session.pool.source('system').drop_index(
   764                 self.session, table, column, unique=True)
   764                 self.session, table, column, unique=True)
   765                 
   765 
   766     def commit_event(self):
   766     def commit_event(self):
   767         self.constraints.remove(self.cstr)
   767         self.constraints.remove(self.cstr)
   768 
   768 
   769 
   769 
   770 def before_delete_constrained_by(session, fromeid, rtype, toeid):
   770 def before_delete_constrained_by(session, fromeid, rtype, toeid):
   782 
   782 
   783 def after_add_constrained_by(session, fromeid, rtype, toeid):
   783 def after_add_constrained_by(session, fromeid, rtype, toeid):
   784     if fromeid in session.query_data('neweids', ()):
   784     if fromeid in session.query_data('neweids', ()):
   785         session.add_query_data(fromeid, toeid)
   785         session.add_query_data(fromeid, toeid)
   786 
   786 
   787     
   787 
   788 # schema permissions synchronization ##########################################
   788 # schema permissions synchronization ##########################################
   789 
   789 
   790 class PermissionOp(Operation):
   790 class PermissionOp(Operation):
   791     """base class to synchronize schema permission definitions"""
   791     """base class to synchronize schema permission definitions"""
   792     def __init__(self, session, perm, etype_eid):
   792     def __init__(self, session, perm, etype_eid):
   803     """synchronize schema when a *_permission relation has been added on a group
   803     """synchronize schema when a *_permission relation has been added on a group
   804     """
   804     """
   805     def __init__(self, session, perm, etype_eid, group_eid):
   805     def __init__(self, session, perm, etype_eid, group_eid):
   806         self.group = entity_name(session, group_eid)
   806         self.group = entity_name(session, group_eid)
   807         PermissionOp.__init__(self, session, perm, etype_eid)
   807         PermissionOp.__init__(self, session, perm, etype_eid)
   808         
   808 
   809     def commit_event(self):
   809     def commit_event(self):
   810         """the observed connections pool has been commited"""
   810         """the observed connections pool has been commited"""
   811         try:
   811         try:
   812             erschema = self.schema[self.name]
   812             erschema = self.schema[self.name]
   813         except KeyError:
   813         except KeyError:
   814             # duh, schema not found, log error and skip operation
   814             # duh, schema not found, log error and skip operation
   815             self.error('no schema for %s', self.name)
   815             self.error('no schema for %s', self.name)
   816             return
   816             return
   817         groups = list(erschema.get_groups(self.perm))
   817         groups = list(erschema.get_groups(self.perm))
   818         try:            
   818         try:
   819             groups.index(self.group)
   819             groups.index(self.group)
   820             self.warning('group %s already have permission %s on %s',
   820             self.warning('group %s already have permission %s on %s',
   821                          self.group, self.perm, erschema.type)
   821                          self.group, self.perm, erschema.type)
   822         except ValueError:
   822         except ValueError:
   823             groups.append(self.group)
   823             groups.append(self.group)
   828     expression
   828     expression
   829     """
   829     """
   830     def __init__(self, session, perm, etype_eid, expression):
   830     def __init__(self, session, perm, etype_eid, expression):
   831         self.expr = expression
   831         self.expr = expression
   832         PermissionOp.__init__(self, session, perm, etype_eid)
   832         PermissionOp.__init__(self, session, perm, etype_eid)
   833         
   833 
   834     def commit_event(self):
   834     def commit_event(self):
   835         """the observed connections pool has been commited"""
   835         """the observed connections pool has been commited"""
   836         try:
   836         try:
   837             erschema = self.schema[self.name]
   837             erschema = self.schema[self.name]
   838         except KeyError:
   838         except KeyError:
   850         AddGroupPermissionOp(session, perm, subject, object)
   850         AddGroupPermissionOp(session, perm, subject, object)
   851     else: # RQLExpression
   851     else: # RQLExpression
   852         expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
   852         expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
   853                                {'x': object}, 'x')[0][0]
   853                                {'x': object}, 'x')[0][0]
   854         AddRQLExpressionPermissionOp(session, perm, subject, expr)
   854         AddRQLExpressionPermissionOp(session, perm, subject, expr)
   855     
   855 
   856 
   856 
   857         
   857 
   858 class DelGroupPermissionOp(AddGroupPermissionOp):
   858 class DelGroupPermissionOp(AddGroupPermissionOp):
   859     """synchronize schema when a *_permission relation has been deleted from a group"""
   859     """synchronize schema when a *_permission relation has been deleted from a group"""
   860         
   860 
   861     def commit_event(self):
   861     def commit_event(self):
   862         """the observed connections pool has been commited"""
   862         """the observed connections pool has been commited"""
   863         try:
   863         try:
   864             erschema = self.schema[self.name]
   864             erschema = self.schema[self.name]
   865         except KeyError:
   865         except KeyError:
   866             # duh, schema not found, log error and skip operation
   866             # duh, schema not found, log error and skip operation
   867             self.error('no schema for %s', self.name)
   867             self.error('no schema for %s', self.name)
   868             return
   868             return
   869         groups = list(erschema.get_groups(self.perm))
   869         groups = list(erschema.get_groups(self.perm))
   870         try:            
   870         try:
   871             groups.remove(self.group)
   871             groups.remove(self.group)
   872             erschema.set_groups(self.perm, groups)
   872             erschema.set_groups(self.perm, groups)
   873         except ValueError:
   873         except ValueError:
   874             self.error('can\'t remove permission %s on %s to group %s',
   874             self.error('can\'t remove permission %s on %s to group %s',
   875                 self.perm, erschema.type, self.group)
   875                 self.perm, erschema.type, self.group)
   876 
   876 
   877         
   877 
   878 class DelRQLExpressionPermissionOp(AddRQLExpressionPermissionOp):
   878 class DelRQLExpressionPermissionOp(AddRQLExpressionPermissionOp):
   879     """synchronize schema when a *_permission relation has been deleted from an rql expression"""
   879     """synchronize schema when a *_permission relation has been deleted from an rql expression"""
   880         
   880 
   881     def commit_event(self):
   881     def commit_event(self):
   882         """the observed connections pool has been commited"""
   882         """the observed connections pool has been commited"""
   883         try:
   883         try:
   884             erschema = self.schema[self.name]
   884             erschema = self.schema[self.name]
   885         except KeyError:
   885         except KeyError:
   895             self.error('can\'t remove permission %s on %s for expression %s',
   895             self.error('can\'t remove permission %s on %s for expression %s',
   896                 self.perm, erschema.type, self.expr)
   896                 self.perm, erschema.type, self.expr)
   897             return
   897             return
   898         erschema.set_rqlexprs(self.perm, rqlexprs)
   898         erschema.set_rqlexprs(self.perm, rqlexprs)
   899 
   899 
   900                 
   900 
   901 def before_del_permission(session, subject, rtype, object):
   901 def before_del_permission(session, subject, rtype, object):
   902     """delete entity/relation *_permission, need to update schema
   902     """delete entity/relation *_permission, need to update schema
   903 
   903 
   904     skip the operation if the related type is being deleted
   904     skip the operation if the related type is being deleted
   905     """
   905     """
   941     hm.register_hook(before_del_eetype, 'before_delete_entity', 'CWEType')
   941     hm.register_hook(before_del_eetype, 'before_delete_entity', 'CWEType')
   942     hm.register_hook(after_del_eetype, 'after_delete_entity', 'CWEType')
   942     hm.register_hook(after_del_eetype, 'after_delete_entity', 'CWEType')
   943     hm.register_hook(before_del_ertype, 'before_delete_entity', 'CWRType')
   943     hm.register_hook(before_del_ertype, 'before_delete_entity', 'CWRType')
   944     hm.register_hook(after_del_relation_type, 'after_delete_relation', 'relation_type')
   944     hm.register_hook(after_del_relation_type, 'after_delete_relation', 'relation_type')
   945     hm.register_hook(rebuild_infered_relations, 'after_add_relation', 'specializes')
   945     hm.register_hook(rebuild_infered_relations, 'after_add_relation', 'specializes')
   946     hm.register_hook(rebuild_infered_relations, 'after_delete_relation', 'specializes')    
   946     hm.register_hook(rebuild_infered_relations, 'after_delete_relation', 'specializes')
   947     # constraints synchronization hooks
   947     # constraints synchronization hooks
   948     hm.register_hook(after_add_econstraint, 'after_add_entity', 'CWConstraint')
   948     hm.register_hook(after_add_econstraint, 'after_add_entity', 'CWConstraint')
   949     hm.register_hook(after_update_econstraint, 'after_update_entity', 'CWConstraint')
   949     hm.register_hook(after_update_econstraint, 'after_update_entity', 'CWConstraint')
   950     hm.register_hook(before_delete_constrained_by, 'before_delete_relation', 'constrained_by')
   950     hm.register_hook(before_delete_constrained_by, 'before_delete_relation', 'constrained_by')
   951     hm.register_hook(after_add_constrained_by, 'after_add_relation', 'constrained_by')
   951     hm.register_hook(after_add_constrained_by, 'after_add_relation', 'constrained_by')