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): |