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""" |