hooks/syncschema.py
changeset 9402 2c48c091b6a2
parent 9080 f0c00b07799a
parent 9375 8e88576787c3
child 9450 af4b93bc38a5
equal deleted inserted replaced
9127:aff75b69db92 9402:2c48c091b6a2
    26 __docformat__ = "restructuredtext en"
    26 __docformat__ = "restructuredtext en"
    27 _ = unicode
    27 _ = unicode
    28 
    28 
    29 from copy import copy
    29 from copy import copy
    30 from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
    30 from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
    31 from yams import buildobjs as ybo, schema2sql as y2sql
    31 from yams import buildobjs as ybo, schema2sql as y2sql, convert_default_value
    32 
    32 
    33 from logilab.common.decorators import clear_cache
    33 from logilab.common.decorators import clear_cache
    34 
    34 
    35 from cubicweb import validation_error
    35 from cubicweb import validation_error
    36 from cubicweb.predicates import is_instance
    36 from cubicweb.predicates import is_instance
    37 from cubicweb.schema import (SCHEMA_TYPES, META_RTYPES, VIRTUAL_RTYPES,
    37 from cubicweb.schema import (SCHEMA_TYPES, META_RTYPES, VIRTUAL_RTYPES,
    38                              CONSTRAINTS, ETYPE_NAME_MAP, display_name)
    38                              CONSTRAINTS, ETYPE_NAME_MAP, display_name)
    39 from cubicweb.server import hook, schemaserial as ss
    39 from cubicweb.server import hook, schemaserial as ss
    40 from cubicweb.server.sqlutils import SQL_PREFIX
    40 from cubicweb.server.sqlutils import SQL_PREFIX
    41 
       
    42 
       
    43 TYPE_CONVERTER = { # XXX
       
    44     'Boolean': bool,
       
    45     'Int': int,
       
    46     'BigInt': int,
       
    47     'Float': float,
       
    48     'Password': str,
       
    49     'String': unicode,
       
    50     'Date' : unicode,
       
    51     'Datetime' : unicode,
       
    52     'Time' : unicode,
       
    53     'TZDatetime' : unicode,
       
    54     'TZTime' : unicode,
       
    55     }
       
    56 
    41 
    57 # core entity and relation types which can't be removed
    42 # core entity and relation types which can't be removed
    58 CORE_TYPES = BASE_TYPES | SCHEMA_TYPES | META_RTYPES | set(
    43 CORE_TYPES = BASE_TYPES | SCHEMA_TYPES | META_RTYPES | set(
    59     ('CWUser', 'CWGroup','login', 'upassword', 'name', 'in_group'))
    44     ('CWUser', 'CWGroup','login', 'upassword', 'name', 'in_group'))
    60 
    45 
   114     object = rschema.schema.eschema(rdefdef.object)
    99     object = rschema.schema.eschema(rdefdef.object)
   115     for specialization in eschema.specialized_by(False):
   100     for specialization in eschema.specialized_by(False):
   116         if (specialization, rdefdef.object) in rschema.rdefs:
   101         if (specialization, rdefdef.object) in rschema.rdefs:
   117             continue
   102             continue
   118         sperdef = RelationDefinitionSchema(specialization, rschema,
   103         sperdef = RelationDefinitionSchema(specialization, rschema,
   119                                            object, props)
   104                                            object, None, values=props)
   120         ss.execschemarql(session.execute, sperdef,
   105         ss.execschemarql(session.execute, sperdef,
   121                          ss.rdef2rql(sperdef, cstrtypemap, groupmap))
   106                          ss.rdef2rql(sperdef, cstrtypemap, groupmap))
   122 
   107 
   123 
   108 
   124 def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
   109 def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
   435         return rdefdef
   420         return rdefdef
   436 
   421 
   437     def precommit_event(self):
   422     def precommit_event(self):
   438         session = self.session
   423         session = self.session
   439         entity = self.entity
   424         entity = self.entity
   440         # entity.defaultval is a string or None, but we need a correctly typed
   425         # entity.defaultval is a Binary or None, but we need a correctly typed
   441         # value
   426         # value
   442         default = entity.defaultval
   427         default = entity.defaultval
   443         if default is not None:
   428         if default is not None:
   444             default = TYPE_CONVERTER[entity.otype.name](default)
   429             default = default.unzpickle()
   445         props = {'default': default,
   430         props = {'default': default,
   446                  'indexed': entity.indexed,
   431                  'indexed': entity.indexed,
   447                  'fulltextindexed': entity.fulltextindexed,
   432                  'fulltextindexed': entity.fulltextindexed,
   448                  'internationalizable': entity.internationalizable}
   433                  'internationalizable': entity.internationalizable}
   449         # update the in-memory schema first
   434         # update the in-memory schema first
   491         rschema = schema.rschema(rdefdef.name)
   476         rschema = schema.rschema(rdefdef.name)
   492         # if relation type has been inserted in the same transaction, its final
   477         # if relation type has been inserted in the same transaction, its final
   493         # attribute is still set to False, so we've to ensure it's False
   478         # attribute is still set to False, so we've to ensure it's False
   494         rschema.final = True
   479         rschema.final = True
   495         insert_rdef_on_subclasses(session, eschema, rschema, rdefdef, props)
   480         insert_rdef_on_subclasses(session, eschema, rschema, rdefdef, props)
   496         # set default value, using sql for performance and to avoid
   481         # update existing entities with the default value of newly added attribute
   497         # modification_date update
   482         if default is not None:
   498         if default:
   483             default = convert_default_value(self.rdefdef, default)
   499             if rdefdef.object in ('Date', 'Datetime', 'TZDatetime'):
   484             session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
   500                 # XXX may may want to use creation_date
   485                                {'default': default})
   501                 if default == 'TODAY':
       
   502                     default = syssource.dbhelper.sql_current_date()
       
   503                 elif default == 'NOW':
       
   504                     default = syssource.dbhelper.sql_current_timestamp()
       
   505                 session.system_sql('UPDATE %s SET %s=%s'
       
   506                                    % (table, column, default))
       
   507             else:
       
   508                 session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
       
   509                                    {'default': default})
       
   510 
   486 
   511     def revertprecommit_event(self):
   487     def revertprecommit_event(self):
   512         # revert changes on in memory schema
   488         # revert changes on in memory schema
   513         if getattr(self, 'rdefdef', None) is None:
   489         if getattr(self, 'rdefdef', None) is None:
   514             return
   490             return
   736             self.unique_changed = True
   712             self.unique_changed = True
   737 
   713 
   738 
   714 
   739 class CWUniqueTogetherConstraintAddOp(MemSchemaOperation):
   715 class CWUniqueTogetherConstraintAddOp(MemSchemaOperation):
   740     entity = None # make pylint happy
   716     entity = None # make pylint happy
       
   717 
   741     def precommit_event(self):
   718     def precommit_event(self):
   742         session = self.session
   719         session = self.session
   743         prefix = SQL_PREFIX
   720         prefix = SQL_PREFIX
   744         table = '%s%s' % (prefix, self.entity.constraint_of[0].name)
   721         entity = self.entity
   745         cols = ['%s%s' % (prefix, r.name) for r in self.entity.relations]
   722         table = '%s%s' % (prefix, entity.constraint_of[0].name)
   746         dbhelper= session.cnxset.source('system').dbhelper
   723         cols = ['%s%s' % (prefix, r.name) for r in entity.relations]
   747         sqls = dbhelper.sqls_create_multicol_unique_index(table, cols)
   724         dbhelper = session.cnxset.source('system').dbhelper
       
   725         sqls = dbhelper.sqls_create_multicol_unique_index(table, cols, entity.name)
   748         for sql in sqls:
   726         for sql in sqls:
   749             session.system_sql(sql)
   727             session.system_sql(sql)
   750 
   728 
   751     # XXX revertprecommit_event
       
   752 
       
   753     def postcommit_event(self):
   729     def postcommit_event(self):
   754         eschema = self.session.vreg.schema.schema_by_eid(self.entity.constraint_of[0].eid)
   730         entity = self.entity
   755         attrs = [r.name for r in self.entity.relations]
   731         eschema = self.session.vreg.schema.schema_by_eid(entity.constraint_of[0].eid)
       
   732         attrs = [r.name for r in entity.relations]
   756         eschema._unique_together.append(attrs)
   733         eschema._unique_together.append(attrs)
   757 
   734 
   758 
   735 
   759 class CWUniqueTogetherConstraintDelOp(MemSchemaOperation):
   736 class CWUniqueTogetherConstraintDelOp(MemSchemaOperation):
   760     entity = oldcstr = None # for pylint
   737     entity = cstrname = None # for pylint
   761     cols = [] # for pylint
   738     cols = () # for pylint
       
   739 
   762     def precommit_event(self):
   740     def precommit_event(self):
   763         session = self.session
   741         session = self.session
   764         prefix = SQL_PREFIX
   742         prefix = SQL_PREFIX
   765         table = '%s%s' % (prefix, self.entity.type)
   743         table = '%s%s' % (prefix, self.entity.type)
   766         dbhelper= session.cnxset.source('system').dbhelper
   744         dbhelper = session.cnxset.source('system').dbhelper
   767         cols = ['%s%s' % (prefix, c) for c in self.cols]
   745         cols = ['%s%s' % (prefix, c) for c in self.cols]
   768         sqls = dbhelper.sqls_drop_multicol_unique_index(table, cols)
   746         sqls = dbhelper.sqls_drop_multicol_unique_index(table, cols, self.cstrname)
   769         for sql in sqls:
   747         for sql in sqls:
   770             try:
   748             session.system_sql(sql)
   771                 session.system_sql(sql)
       
   772             except Exception as exc: # should be ProgrammingError
       
   773                 if sql.startswith('DROP'):
       
   774                     self.error('execute of `%s` failed (cause: %s)', sql, exc)
       
   775                     continue
       
   776                 raise
       
   777 
       
   778     # XXX revertprecommit_event
       
   779 
   749 
   780     def postcommit_event(self):
   750     def postcommit_event(self):
   781         eschema = self.session.vreg.schema.schema_by_eid(self.entity.eid)
   751         eschema = self.session.vreg.schema.schema_by_eid(self.entity.eid)
   782         cols = set(self.cols)
   752         cols = set(self.cols)
   783         unique_together = [ut for ut in eschema._unique_together
   753         unique_together = [ut for ut in eschema._unique_together
  1193         if self._cw.deleted_in_transaction(self.eidto):
  1163         if self._cw.deleted_in_transaction(self.eidto):
  1194             return
  1164             return
  1195         schema = self._cw.vreg.schema
  1165         schema = self._cw.vreg.schema
  1196         cstr = self._cw.entity_from_eid(self.eidfrom)
  1166         cstr = self._cw.entity_from_eid(self.eidfrom)
  1197         entity = schema.schema_by_eid(self.eidto)
  1167         entity = schema.schema_by_eid(self.eidto)
  1198         cols = [r.name for r in cstr.relations]
  1168         cols = tuple(r.name for r in cstr.relations)
  1199         CWUniqueTogetherConstraintDelOp(self._cw, entity=entity,
  1169         CWUniqueTogetherConstraintDelOp(self._cw, entity=entity,
  1200                                         oldcstr=cstr, cols=cols)
  1170                                         cstrname=cstr.name, cols=cols)
  1201 
  1171 
  1202 
  1172 
  1203 # permissions synchronization hooks ############################################
  1173 # permissions synchronization hooks ############################################
  1204 
  1174 
  1205 class AfterAddPermissionHook(SyncSchemaHook):
  1175 class AfterAddPermissionHook(SyncSchemaHook):