hooks/syncschema.py
changeset 4763 81b0df087375
parent 4722 9c13d5db03d9
child 4808 23df4a120c96
equal deleted inserted replaced
4762:8dce25da9d95 4763:81b0df087375
    10 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
    10 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
    11 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
    11 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
    12 """
    12 """
    13 __docformat__ = "restructuredtext en"
    13 __docformat__ = "restructuredtext en"
    14 
    14 
       
    15 from copy import copy
    15 from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
    16 from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
    16 from yams.buildobjs import EntityType, RelationType, RelationDefinition
    17 from yams import buildobjs as ybo, schema2sql as y2sql
    17 from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints
       
    18 
    18 
    19 from logilab.common.decorators import clear_cache
    19 from logilab.common.decorators import clear_cache
       
    20 from logilab.common.testlib import mock_object
    20 
    21 
    21 from cubicweb import ValidationError
    22 from cubicweb import ValidationError
    22 from cubicweb.selectors import implements
    23 from cubicweb.selectors import implements
    23 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CONSTRAINTS, display_name
    24 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CONSTRAINTS, display_name
    24 from cubicweb.server import hook, schemaserial as ss
    25 from cubicweb.server import hook, schemaserial as ss
   244         eidcolumn = SQL_PREFIX + 'eid'
   245         eidcolumn = SQL_PREFIX + 'eid'
   245         if not inlined:
   246         if not inlined:
   246             # need to create the relation if it has not been already done by
   247             # need to create the relation if it has not been already done by
   247             # another event of the same transaction
   248             # another event of the same transaction
   248             if not rschema.type in session.transaction_data.get('createdtables', ()):
   249             if not rschema.type in session.transaction_data.get('createdtables', ()):
   249                 tablesql = rschema2sql(rschema)
   250                 tablesql = y2sql.rschema2sql(rschema)
   250                 # create the necessary table
   251                 # create the necessary table
   251                 for sql in tablesql.split(';'):
   252                 for sql in tablesql.split(';'):
   252                     if sql.strip():
   253                     if sql.strip():
   253                         sqlexec(sql)
   254                         sqlexec(sql)
   254                 session.transaction_data.setdefault('createdtables', []).append(
   255                 session.transaction_data.setdefault('createdtables', []).append(
   312                               'order': entity.ordernum or 0})
   313                               'order': entity.ordernum or 0})
   313         subj = str(fromentity.name)
   314         subj = str(fromentity.name)
   314         rtype = entity.rtype.name
   315         rtype = entity.rtype.name
   315         obj = str(entity.otype.name)
   316         obj = str(entity.otype.name)
   316         constraints = get_constraints(self.session, entity)
   317         constraints = get_constraints(self.session, entity)
   317         rdef = RelationDefinition(subj, rtype, obj,
   318         rdef = ybo.RelationDefinition(subj, rtype, obj,
   318                                   description=entity.description,
   319                                       description=entity.description,
   319                                   cardinality=entity.cardinality,
   320                                       cardinality=entity.cardinality,
   320                                   constraints=constraints,
   321                                       constraints=constraints,
   321                                   order=entity.ordernum,
   322                                       order=entity.ordernum,
   322                                   eid=entity.eid,
   323                                       eid=entity.eid,
   323                                   **kwargs)
   324                                       **kwargs)
   324         MemSchemaRDefAdd(self.session, rdef)
   325         MemSchemaRDefAdd(self.session, rdef)
   325         return rdef
   326         return rdef
   326 
   327 
   327     def precommit_event(self):
   328     def precommit_event(self):
   328         session = self.session
   329         session = self.session
   336                  'indexed': entity.indexed,
   337                  'indexed': entity.indexed,
   337                  'fulltextindexed': entity.fulltextindexed,
   338                  'fulltextindexed': entity.fulltextindexed,
   338                  'internationalizable': entity.internationalizable}
   339                  'internationalizable': entity.internationalizable}
   339         rdef = self.init_rdef(**props)
   340         rdef = self.init_rdef(**props)
   340         sysource = session.pool.source('system')
   341         sysource = session.pool.source('system')
   341         attrtype = type_from_constraints(sysource.dbhelper, rdef.object,
   342         attrtype = y2sql.type_from_constraints(
   342                                          rdef.constraints)
   343             sysource.dbhelper, rdef.object, rdef.constraints)
   343         # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
   344         # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
   344         # add a new column with UNIQUE, it should be added after the ALTER TABLE
   345         # add a new column with UNIQUE, it should be added after the ALTER TABLE
   345         # using ADD INDEX
   346         # using ADD INDEX
   346         if sysource.dbdriver == 'sqlite' and 'UNIQUE' in attrtype:
   347         if sysource.dbdriver == 'sqlite' and 'UNIQUE' in attrtype:
   347             extra_unique_index = True
   348             extra_unique_index = True
   368                                       unique=extra_unique_index)
   369                                       unique=extra_unique_index)
   369             except Exception, ex:
   370             except Exception, ex:
   370                 self.error('error while creating index for %s.%s: %s',
   371                 self.error('error while creating index for %s.%s: %s',
   371                            table, column, ex)
   372                            table, column, ex)
   372         # final relations are not infered, propagate
   373         # final relations are not infered, propagate
   373         try:
   374         schema = session.vreg.schema
   374             eschema = session.vreg.schema.eschema(rdef.subject)
   375         try:
       
   376             eschema = schema.eschema(rdef.subject)
   375         except KeyError:
   377         except KeyError:
   376             return # entity type currently being added
   378             return # entity type currently being added
   377         # propagate attribute to children classes
   379         # propagate attribute to children classes
   378         rschema = session.vreg.schema.rschema(rdef.name)
   380         rschema = schema.rschema(rdef.name)
   379         # if relation type has been inserted in the same transaction, its final
   381         # if relation type has been inserted in the same transaction, its final
   380         # attribute is still set to False, so we've to ensure it's False
   382         # attribute is still set to False, so we've to ensure it's False
   381         rschema.final = True
   383         rschema.final = True
   382         # XXX 'infered': True/False, not clear actually
   384         # XXX 'infered': True/False, not clear actually
   383         props.update({'constraints': rdef.constraints,
   385         props.update({'constraints': rdef.constraints,
   384                       'description': rdef.description,
   386                       'description': rdef.description,
   385                       'cardinality': rdef.cardinality,
   387                       'cardinality': rdef.cardinality,
   386                       'constraints': rdef.constraints,
   388                       'constraints': rdef.constraints,
   387                       'permissions': rdef.get_permissions(),
   389                       'permissions': rdef.get_permissions(),
   388                       'order': rdef.order})
   390                       'order': rdef.order,
       
   391                       'infered': False, 'eid': None
       
   392                       })
       
   393         cstrtypemap = ss.cstrtype_mapping(session)
   389         groupmap = group_mapping(session)
   394         groupmap = group_mapping(session)
       
   395         object = schema.eschema(rdef.object)
   390         for specialization in eschema.specialized_by(False):
   396         for specialization in eschema.specialized_by(False):
   391             if (specialization, rdef.object) in rschema.rdefs:
   397             if (specialization, rdef.object) in rschema.rdefs:
   392                 continue
   398                 continue
   393             sperdef = RelationDefinitionSchema(specialization, rschema, rdef.object, props)
   399             sperdef = RelationDefinitionSchema(specialization, rschema,
   394             for rql, args in ss.rdef2rql(rschema, str(specialization),
   400                                                object, props)
   395                                          rdef.object, sperdef, groupmap=groupmap):
   401             ss.execschemarql(session.execute, sperdef,
   396                 session.execute(rql, args)
   402                              ss.rdef2rql(sperdef, cstrtypemap, groupmap))
   397         # set default value, using sql for performance and to avoid
   403         # set default value, using sql for performance and to avoid
   398         # modification_date update
   404         # modification_date update
   399         if default:
   405         if default:
   400             session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
   406             session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
   401                                {'default': default})
   407                                {'default': default})
   440             # transaction
   446             # transaction
   441             if not (rschema.subjects() or
   447             if not (rschema.subjects() or
   442                     rtype in session.transaction_data.get('createdtables', ())):
   448                     rtype in session.transaction_data.get('createdtables', ())):
   443                 try:
   449                 try:
   444                     rschema = schema.rschema(rtype)
   450                     rschema = schema.rschema(rtype)
   445                     tablesql = rschema2sql(rschema)
   451                     tablesql = y2sql.rschema2sql(rschema)
   446                 except KeyError:
   452                 except KeyError:
   447                     # fake we add it to the schema now to get a correctly
   453                     # fake we add it to the schema now to get a correctly
   448                     # initialized schema but remove it before doing anything
   454                     # initialized schema but remove it before doing anything
   449                     # more dangerous...
   455                     # more dangerous...
   450                     rschema = schema.add_relation_type(rdef)
   456                     rschema = schema.add_relation_type(rdef)
   451                     tablesql = rschema2sql(rschema)
   457                     tablesql = y2sql.rschema2sql(rschema)
   452                     schema.del_relation_type(rtype)
   458                     schema.del_relation_type(rtype)
   453                 # create the necessary table
   459                 # create the necessary table
   454                 for sql in tablesql.split(';'):
   460                 for sql in tablesql.split(';'):
   455                     if sql.strip():
   461                     if sql.strip():
   456                         session.system_sql(sql)
   462                         session.system_sql(sql)
   478                 # not supported (and NOT NULL not set by yams in that case, so
   484                 # not supported (and NOT NULL not set by yams in that case, so
   479                 # no worry)
   485                 # no worry)
   480                 return
   486                 return
   481             atype = self.rschema.objects(etype)[0]
   487             atype = self.rschema.objects(etype)[0]
   482             constraints = self.rschema.rdef(etype, atype).constraints
   488             constraints = self.rschema.rdef(etype, atype).constraints
   483             coltype = type_from_constraints(adbh, atype, constraints,
   489             coltype = y2sql.type_from_constraints(adbh, atype, constraints,
   484                                             creating=False)
   490                                                   creating=False)
   485             # XXX check self.values['cardinality'][0] actually changed?
   491             # XXX check self.values['cardinality'][0] actually changed?
   486             sql = adbh.sql_set_null_allowed(table, column, coltype,
   492             notnull = self.values['cardinality'][0] != '1'
   487                                             self.values['cardinality'][0] != '1')
   493             sql = adbh.sql_set_null_allowed(table, column, coltype, notnull)
   488             self.session.system_sql(sql)
   494             self.session.system_sql(sql)
   489         if 'fulltextindexed' in self.values:
   495         if 'fulltextindexed' in self.values:
   490             UpdateFTIndexOp(self.session)
   496             UpdateFTIndexOp(self.session)
   491             self.session.transaction_data.setdefault('fti_update_etypes',
   497             self.session.transaction_data.setdefault('fti_update_etypes',
   492                                                      set()).add(etype)
   498                                                      set()).add(etype)
   515         # alter the physical schema on size constraint changes
   521         # alter the physical schema on size constraint changes
   516         if newcstr.type() == 'SizeConstraint' and (
   522         if newcstr.type() == 'SizeConstraint' and (
   517             oldcstr is None or oldcstr.max != newcstr.max):
   523             oldcstr is None or oldcstr.max != newcstr.max):
   518             adbh = self.session.pool.source('system').dbhelper
   524             adbh = self.session.pool.source('system').dbhelper
   519             card = rtype.rdef(subjtype, objtype).cardinality
   525             card = rtype.rdef(subjtype, objtype).cardinality
   520             coltype = type_from_constraints(adbh, objtype, [newcstr],
   526             coltype = y2sql.type_from_constraints(adbh, objtype, [newcstr],
   521                                             creating=False)
   527                                                   creating=False)
   522             sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
   528             sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
   523             try:
   529             try:
   524                 session.system_sql(sql, rollback_on_failure=False)
   530                 session.system_sql(sql, rollback_on_failure=False)
   525                 self.info('altered column %s of table %s: now VARCHAR(%s)',
   531                 self.info('altered column %s of table %s: now VARCHAR(%s)',
   526                           column, table, newcstr.max)
   532                           column, table, newcstr.max)
   817         entity = self.entity
   823         entity = self.entity
   818         if entity.get('final'):
   824         if entity.get('final'):
   819             return
   825             return
   820         schema = self._cw.vreg.schema
   826         schema = self._cw.vreg.schema
   821         name = entity['name']
   827         name = entity['name']
   822         etype = EntityType(name=name, description=entity.get('description'),
   828         etype = ybo.EntityType(name=name, description=entity.get('description'),
   823                            meta=entity.get('meta')) # don't care about final
   829                                meta=entity.get('meta')) # don't care about final
   824         # fake we add it to the schema now to get a correctly initialized schema
   830         # fake we add it to the schema now to get a correctly initialized schema
   825         # but remove it before doing anything more dangerous...
   831         # but remove it before doing anything more dangerous...
   826         schema = self._cw.vreg.schema
   832         schema = self._cw.vreg.schema
   827         eschema = schema.add_entity_type(etype)
   833         eschema = schema.add_entity_type(etype)
   828         # generate table sql and rql to add metadata
   834         # generate table sql and rql to add metadata
   829         tablesql = eschema2sql(self._cw.pool.source('system').dbhelper, eschema,
   835         tablesql = y2sql.eschema2sql(self._cw.pool.source('system').dbhelper,
   830                                prefix=SQL_PREFIX)
   836                                      eschema, prefix=SQL_PREFIX)
   831         relrqls = []
   837         rdefrqls = []
       
   838         gmap = group_mapping(self._cw)
       
   839         cmap = ss.cstrtype_mapping(self._cw)
   832         for rtype in (META_RTYPES - VIRTUAL_RTYPES):
   840         for rtype in (META_RTYPES - VIRTUAL_RTYPES):
   833             rschema = schema[rtype]
   841             rschema = schema[rtype]
   834             sampletype = rschema.subjects()[0]
   842             sampletype = rschema.subjects()[0]
   835             desttype = rschema.objects()[0]
   843             desttype = rschema.objects()[0]
   836             props = rschema.rdef(sampletype, desttype)
   844             rdef = copy(rschema.rdef(sampletype, desttype))
   837             relrqls += list(ss.rdef2rql(rschema, name, desttype, props,
   845             rdef.subject = mock_object(eid=entity.eid)
   838                                         groupmap=group_mapping(self._cw)))
   846             mock = mock_object(eid=None)
       
   847             rdefrqls.append( (mock, tuple(ss.rdef2rql(rdef, cmap, gmap))) )
   839         # now remove it !
   848         # now remove it !
   840         schema.del_entity_type(name)
   849         schema.del_entity_type(name)
   841         # create the necessary table
   850         # create the necessary table
   842         for sql in tablesql.split(';'):
   851         for sql in tablesql.split(';'):
   843             if sql.strip():
   852             if sql.strip():
   846         # this have to be done before adding other relations definitions
   855         # this have to be done before adding other relations definitions
   847         # or permission settings
   856         # or permission settings
   848         etype.eid = entity.eid
   857         etype.eid = entity.eid
   849         MemSchemaCWETypeAdd(self._cw, etype)
   858         MemSchemaCWETypeAdd(self._cw, etype)
   850         # add meta relations
   859         # add meta relations
   851         for rql, kwargs in relrqls:
   860         for rdef, relrqls in rdefrqls:
   852             self._cw.execute(rql, kwargs)
   861             ss.execschemarql(self._cw.execute, rdef, relrqls)
   853 
   862 
   854 
   863 
   855 class BeforeUpdateCWETypeHook(DelCWETypeHook):
   864 class BeforeUpdateCWETypeHook(DelCWETypeHook):
   856     """check name change, handle final"""
   865     """check name change, handle final"""
   857     __regid__ = 'syncupdatecwetype'
   866     __regid__ = 'syncupdatecwetype'
   904     __regid__ = 'syncaddcwrtype'
   913     __regid__ = 'syncaddcwrtype'
   905     events = ('after_add_entity',)
   914     events = ('after_add_entity',)
   906 
   915 
   907     def __call__(self):
   916     def __call__(self):
   908         entity = self.entity
   917         entity = self.entity
   909         rtype = RelationType(name=entity.name,
   918         rtype = ybo.RelationType(name=entity.name,
   910                              description=entity.get('description'),
   919                                  description=entity.get('description'),
   911                              meta=entity.get('meta', False),
   920                                  meta=entity.get('meta', False),
   912                              inlined=entity.get('inlined', False),
   921                                  inlined=entity.get('inlined', False),
   913                              symmetric=entity.get('symmetric', False),
   922                                  symmetric=entity.get('symmetric', False),
   914                              eid=entity.eid)
   923                                  eid=entity.eid)
   915         MemSchemaCWRTypeAdd(self._cw, rtype)
   924         MemSchemaCWRTypeAdd(self._cw, rtype)
   916 
   925 
   917 
   926 
   918 class BeforeUpdateCWRTypeHook(DelCWRTypeHook):
   927 class BeforeUpdateCWRTypeHook(DelCWRTypeHook):
   919     """check name change, handle final"""
   928     """check name change, handle final"""