67 |
66 |
68 class ClearGroupMap(hook.Hook): |
67 class ClearGroupMap(hook.Hook): |
69 __regid__ = 'cw.migration.clear_group_mapping' |
68 __regid__ = 'cw.migration.clear_group_mapping' |
70 __select__ = hook.Hook.__select__ & is_instance('CWGroup') |
69 __select__ = hook.Hook.__select__ & is_instance('CWGroup') |
71 events = ('after_add_entity', 'after_update_entity',) |
70 events = ('after_add_entity', 'after_update_entity',) |
|
71 |
72 def __call__(self): |
72 def __call__(self): |
73 clear_cache(self.mih, 'group_mapping') |
73 clear_cache(self.mih, 'group_mapping') |
74 self.mih._synchronized.clear() |
74 self.mih._synchronized.clear() |
75 |
75 |
76 @classmethod |
76 @classmethod |
77 def mih_register(cls, repo): |
77 def mih_register(cls, repo): |
78 # may be already registered in tests (e.g. unittest_migractions at |
78 # may be already registered in tests (e.g. unittest_migractions at |
79 # least) |
79 # least) |
80 if not cls.__regid__ in repo.vreg['after_add_entity_hooks']: |
80 if cls.__regid__ not in repo.vreg['after_add_entity_hooks']: |
81 repo.vreg.register(ClearGroupMap) |
81 repo.vreg.register(ClearGroupMap) |
82 |
82 |
83 |
83 |
84 class ServerMigrationHelper(MigrationHelper): |
84 class ServerMigrationHelper(MigrationHelper): |
85 """specific migration helper for server side migration scripts, |
85 """specific migration helper for server side migration scripts, |
174 super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options) |
174 super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options) |
175 |
175 |
176 def cmd_process_script(self, migrscript, funcname=None, *args, **kwargs): |
176 def cmd_process_script(self, migrscript, funcname=None, *args, **kwargs): |
177 try: |
177 try: |
178 return super(ServerMigrationHelper, self).cmd_process_script( |
178 return super(ServerMigrationHelper, self).cmd_process_script( |
179 migrscript, funcname, *args, **kwargs) |
179 migrscript, funcname, *args, **kwargs) |
180 except ExecutionError as err: |
180 except ExecutionError as err: |
181 sys.stderr.write("-> %s\n" % err) |
181 sys.stderr.write("-> %s\n" % err) |
182 except BaseException: |
182 except BaseException: |
183 self.rollback() |
183 self.rollback() |
184 raise |
184 raise |
204 print('-> no backup done.') |
204 print('-> no backup done.') |
205 return |
205 return |
206 elif askconfirm and not self.confirm('Backup %s database?' % config.appid): |
206 elif askconfirm and not self.confirm('Backup %s database?' % config.appid): |
207 print('-> no backup done.') |
207 print('-> no backup done.') |
208 return |
208 return |
209 open(backupfile,'w').close() # kinda lock |
209 open(backupfile,'w').close() # kinda lock |
210 os.chmod(backupfile, 0o600) |
210 os.chmod(backupfile, 0o600) |
211 # backup |
211 # backup |
212 source = repo.system_source |
212 source = repo.system_source |
213 tmpdir = tempfile.mkdtemp() |
213 tmpdir = tempfile.mkdtemp() |
214 try: |
214 try: |
233 bkup.add(osp.join(tmpdir, filename), filename) |
233 bkup.add(osp.join(tmpdir, filename), filename) |
234 bkup.close() |
234 bkup.close() |
235 # call hooks |
235 # call hooks |
236 repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp) |
236 repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp) |
237 # done |
237 # done |
238 print('-> backup file', backupfile) |
238 print('-> backup file', backupfile) |
239 finally: |
239 finally: |
240 shutil.rmtree(tmpdir) |
240 shutil.rmtree(tmpdir) |
241 |
241 |
242 def restore_database(self, backupfile, drop=True, askconfirm=True, format='native'): |
242 def restore_database(self, backupfile, drop=True, askconfirm=True, format='native'): |
243 # check |
243 # check |
255 shutil.copy(backupfile, osp.join(tmpdir, 'system')) |
255 shutil.copy(backupfile, osp.join(tmpdir, 'system')) |
256 else: |
256 else: |
257 for name in bkup.getnames(): |
257 for name in bkup.getnames(): |
258 if name[0] in '/.': |
258 if name[0] in '/.': |
259 raise ExecutionError('Security check failed, path starts with "/" or "."') |
259 raise ExecutionError('Security check failed, path starts with "/" or "."') |
260 bkup.close() # XXX seek error if not close+open !?! |
260 bkup.close() # XXX seek error if not close+open !?! |
261 bkup = tarfile.open(backupfile, 'r|gz') |
261 bkup = tarfile.open(backupfile, 'r|gz') |
262 bkup.extractall(path=tmpdir) |
262 bkup.extractall(path=tmpdir) |
263 bkup.close() |
263 bkup.close() |
264 if osp.isfile(osp.join(tmpdir, 'format.txt')): |
264 if osp.isfile(osp.join(tmpdir, 'format.txt')): |
265 with open(osp.join(tmpdir, 'format.txt')) as format_file: |
265 with open(osp.join(tmpdir, 'format.txt')) as format_file: |
389 # handle groups |
389 # handle groups |
390 newgroups = list(erschema.get_groups(action)) |
390 newgroups = list(erschema.get_groups(action)) |
391 for geid, gname in self.rqlexec('Any G, GN WHERE T %s G, G name GN, ' |
391 for geid, gname in self.rqlexec('Any G, GN WHERE T %s G, G name GN, ' |
392 'T eid %%(x)s' % perm, {'x': teid}, |
392 'T eid %%(x)s' % perm, {'x': teid}, |
393 ask_confirm=False): |
393 ask_confirm=False): |
394 if not gname in newgroups: |
394 if gname not in newgroups: |
395 if not confirm or self.confirm('Remove %s permission of %s to %s?' |
395 if not confirm or self.confirm('Remove %s permission of %s to %s?' |
396 % (action, erschema, gname)): |
396 % (action, erschema, gname)): |
397 self.rqlexec('DELETE T %s G WHERE G eid %%(x)s, T eid %s' |
397 self.rqlexec('DELETE T %s G WHERE G eid %%(x)s, T eid %s' |
398 % (perm, teid), |
398 % (perm, teid), |
399 {'x': geid}, ask_confirm=False) |
399 {'x': geid}, ask_confirm=False) |
412 # handle rql expressions |
412 # handle rql expressions |
413 newexprs = dict((expr.expression, expr) for expr in erschema.get_rqlexprs(action)) |
413 newexprs = dict((expr.expression, expr) for expr in erschema.get_rqlexprs(action)) |
414 for expreid, expression in self.rqlexec('Any E, EX WHERE T %s E, E expression EX, ' |
414 for expreid, expression in self.rqlexec('Any E, EX WHERE T %s E, E expression EX, ' |
415 'T eid %s' % (perm, teid), |
415 'T eid %s' % (perm, teid), |
416 ask_confirm=False): |
416 ask_confirm=False): |
417 if not expression in newexprs: |
417 if expression not in newexprs: |
418 if not confirm or self.confirm('Remove %s expression for %s permission of %s?' |
418 if not confirm or self.confirm('Remove %s expression for %s permission of %s?' |
419 % (expression, action, erschema)): |
419 % (expression, action, erschema)): |
420 # deleting the relation will delete the expression entity |
420 # deleting the relation will delete the expression entity |
421 self.rqlexec('DELETE T %s E WHERE E eid %%(x)s, T eid %s' |
421 self.rqlexec('DELETE T %s E WHERE E eid %%(x)s, T eid %s' |
422 % (perm, teid), |
422 % (perm, teid), |
456 rschema = self.fs_schema.rschema(rtype) |
456 rschema = self.fs_schema.rschema(rtype) |
457 reporschema = self.repo.schema.rschema(rtype) |
457 reporschema = self.repo.schema.rschema(rtype) |
458 if syncprops: |
458 if syncprops: |
459 assert reporschema.eid, reporschema |
459 assert reporschema.eid, reporschema |
460 self.rqlexecall(ss.updaterschema2rql(rschema, reporschema.eid), |
460 self.rqlexecall(ss.updaterschema2rql(rschema, reporschema.eid), |
461 ask_confirm=self.verbosity>=2) |
461 ask_confirm=self.verbosity >= 2) |
462 if rschema.rule: |
462 if rschema.rule: |
463 if syncperms: |
463 if syncperms: |
464 self._synchronize_permissions(rschema, reporschema.eid) |
464 self._synchronize_permissions(rschema, reporschema.eid) |
465 elif syncrdefs: |
465 elif syncrdefs: |
466 for subj, obj in rschema.rdefs: |
466 for subj, obj in rschema.rdefs: |
490 self._synchronized.add(etype) |
490 self._synchronized.add(etype) |
491 repoeschema = self.repo.schema.eschema(etype) |
491 repoeschema = self.repo.schema.eschema(etype) |
492 try: |
492 try: |
493 eschema = self.fs_schema.eschema(etype) |
493 eschema = self.fs_schema.eschema(etype) |
494 except KeyError: |
494 except KeyError: |
495 return # XXX somewhat unexpected, no?... |
495 return # XXX somewhat unexpected, no?... |
496 if syncprops: |
496 if syncprops: |
497 repospschema = repoeschema.specializes() |
497 repospschema = repoeschema.specializes() |
498 espschema = eschema.specializes() |
498 espschema = eschema.specializes() |
499 if repospschema and not espschema: |
499 if repospschema and not espschema: |
500 self.rqlexec('DELETE X specializes Y WHERE X is CWEType, X name %(x)s', |
500 self.rqlexec('DELETE X specializes Y WHERE X is CWEType, X name %(x)s', |
511 if syncrdefs: |
511 if syncrdefs: |
512 for rschema, targettypes, role in eschema.relation_definitions(True): |
512 for rschema, targettypes, role in eschema.relation_definitions(True): |
513 if rschema in VIRTUAL_RTYPES: |
513 if rschema in VIRTUAL_RTYPES: |
514 continue |
514 continue |
515 if role == 'subject': |
515 if role == 'subject': |
516 if not rschema in repoeschema.subject_relations(): |
516 if rschema not in repoeschema.subject_relations(): |
517 continue |
517 continue |
518 subjtypes, objtypes = [etype], targettypes |
518 subjtypes, objtypes = [etype], targettypes |
519 else: # role == 'object' |
519 else: # role == 'object' |
520 if not rschema in repoeschema.object_relations(): |
520 if rschema not in repoeschema.object_relations(): |
521 continue |
521 continue |
522 subjtypes, objtypes = targettypes, [etype] |
522 subjtypes, objtypes = targettypes, [etype] |
523 self._synchronize_rschema(rschema, syncrdefs=False, |
523 self._synchronize_rschema(rschema, syncrdefs=False, |
524 syncprops=syncprops, syncperms=syncperms) |
524 syncprops=syncprops, syncperms=syncperms) |
525 if rschema.rule: # rdef for computed rtype are infered hence should not be |
525 if rschema.rule: # rdef for computed rtype are infered hence should not be |
526 # synchronized |
526 # synchronized |
527 continue |
527 continue |
528 reporschema = self.repo.schema.rschema(rschema) |
528 reporschema = self.repo.schema.rschema(rschema) |
529 for subj in subjtypes: |
529 for subj in subjtypes: |
530 for obj in objtypes: |
530 for obj in objtypes: |
531 if (subj, obj) not in reporschema.rdefs: |
531 if (subj, obj) not in reporschema.rdefs: |
532 continue |
532 continue |
533 self._synchronize_rdef_schema(subj, rschema, obj, |
533 self._synchronize_rdef_schema(subj, rschema, obj, |
534 syncprops=syncprops, syncperms=syncperms) |
534 syncprops=syncprops, syncperms=syncperms) |
535 if syncprops: # need to process __unique_together__ after rdefs were processed |
535 if syncprops: # need to process __unique_together__ after rdefs were processed |
536 # mappings from constraint name to columns |
536 # mappings from constraint name to columns |
537 # filesystem (fs) and repository (repo) wise |
537 # filesystem (fs) and repository (repo) wise |
538 fs = {} |
538 fs = {} |
539 repo = {} |
539 repo = {} |
540 for cols in eschema._unique_together or (): |
540 for cols in eschema._unique_together or (): |
590 self._synchronized.add((subjtype, rschema, objtype)) |
590 self._synchronized.add((subjtype, rschema, objtype)) |
591 if rschema.symmetric: |
591 if rschema.symmetric: |
592 self._synchronized.add((objtype, rschema, subjtype)) |
592 self._synchronized.add((objtype, rschema, subjtype)) |
593 rdef = rschema.rdef(subjtype, objtype) |
593 rdef = rschema.rdef(subjtype, objtype) |
594 if rdef.infered: |
594 if rdef.infered: |
595 return # don't try to synchronize infered relation defs |
595 return # don't try to synchronize infered relation defs |
596 repordef = reporschema.rdef(subjtype, objtype) |
596 repordef = reporschema.rdef(subjtype, objtype) |
597 confirm = self.verbosity >= 2 |
597 confirm = self.verbosity >= 2 |
598 if syncprops: |
598 if syncprops: |
599 # properties |
599 # properties |
600 self.rqlexecall(ss.updaterdef2rql(rdef, repordef.eid), |
600 self.rqlexecall(ss.updaterdef2rql(rdef, repordef.eid), |
617 # 2. add new constraints |
617 # 2. add new constraints |
618 cstrtype_map = self.cstrtype_mapping() |
618 cstrtype_map = self.cstrtype_mapping() |
619 self.rqlexecall(ss.constraints2rql(cstrtype_map, newconstraints, |
619 self.rqlexecall(ss.constraints2rql(cstrtype_map, newconstraints, |
620 repordef.eid), |
620 repordef.eid), |
621 ask_confirm=confirm) |
621 ask_confirm=confirm) |
622 if syncperms and not rschema in VIRTUAL_RTYPES: |
622 if syncperms and rschema not in VIRTUAL_RTYPES: |
623 self._synchronize_permissions(rdef, repordef.eid) |
623 self._synchronize_permissions(rdef, repordef.eid) |
624 |
624 |
625 # base actions ############################################################ |
625 # base actions ############################################################ |
626 |
626 |
627 def checkpoint(self, ask_confirm=True): |
627 def checkpoint(self, ask_confirm=True): |
628 """checkpoint action""" |
628 """checkpoint action""" |
629 if not ask_confirm or self.confirm('Commit now ?', shell=False): |
629 if not ask_confirm or self.confirm('Commit now ?', shell=False): |
630 self.commit() |
630 self.commit() |
631 |
631 |
632 def cmd_add_cube(self, cube, update_database=True): |
632 def cmd_add_cube(self, cube, update_database=True): |
633 self.cmd_add_cubes( (cube,), update_database) |
633 self.cmd_add_cubes((cube,), update_database) |
634 |
634 |
635 def cmd_add_cubes(self, cubes, update_database=True): |
635 def cmd_add_cubes(self, cubes, update_database=True): |
636 """update_database is telling if the database schema should be updated |
636 """update_database is telling if the database schema should be updated |
637 or if only the relevant eproperty should be inserted (for the case where |
637 or if only the relevant eproperty should be inserted (for the case where |
638 a cube has been extracted from an existing instance, so the |
638 a cube has been extracted from an existing instance, so the |
640 """ |
640 """ |
641 newcubes = super(ServerMigrationHelper, self).cmd_add_cubes(cubes) |
641 newcubes = super(ServerMigrationHelper, self).cmd_add_cubes(cubes) |
642 if not newcubes: |
642 if not newcubes: |
643 return |
643 return |
644 for cube in newcubes: |
644 for cube in newcubes: |
645 self.cmd_set_property('system.version.'+cube, |
645 self.cmd_set_property('system.version.' + cube, |
646 self.config.cube_version(cube)) |
646 self.config.cube_version(cube)) |
647 # ensure added cube is in config cubes |
647 # ensure added cube is in config cubes |
648 # XXX worth restoring on error? |
648 # XXX worth restoring on error? |
649 if not cube in self.config._cubes: |
649 if cube not in self.config._cubes: |
650 self.config._cubes += (cube,) |
650 self.config._cubes += (cube,) |
651 if not update_database: |
651 if not update_database: |
652 self.commit() |
652 self.commit() |
653 return |
653 return |
654 newcubes_schema = self.config.load_schema(construction_mode='non-strict') |
654 newcubes_schema = self.config.load_schema(construction_mode='non-strict') |
656 # etc. and fsschema of migration script contexts |
656 # etc. and fsschema of migration script contexts |
657 self.fs_schema = newcubes_schema |
657 self.fs_schema = newcubes_schema |
658 self.update_context('fsschema', self.fs_schema) |
658 self.update_context('fsschema', self.fs_schema) |
659 new = set() |
659 new = set() |
660 # execute pre-create files |
660 # execute pre-create files |
661 driver = self.repo.system_source.dbdriver |
|
662 for cube in reversed(newcubes): |
661 for cube in reversed(newcubes): |
663 self.cmd_install_custom_sql_scripts(cube) |
662 self.cmd_install_custom_sql_scripts(cube) |
664 self.cmd_exec_event_script('precreate', cube) |
663 self.cmd_exec_event_script('precreate', cube) |
665 # add new entity and relation types |
664 # add new entity and relation types |
666 for rschema in newcubes_schema.relations(): |
665 for rschema in newcubes_schema.relations(): |
667 if not rschema in self.repo.schema: |
666 if rschema not in self.repo.schema: |
668 self.cmd_add_relation_type(rschema.type) |
667 self.cmd_add_relation_type(rschema.type) |
669 new.add(rschema.type) |
668 new.add(rschema.type) |
670 toadd = [eschema for eschema in newcubes_schema.entities() |
669 toadd = [eschema for eschema in newcubes_schema.entities() |
671 if not eschema in self.repo.schema] |
670 if eschema not in self.repo.schema] |
672 for eschema in order_eschemas(toadd): |
671 for eschema in order_eschemas(toadd): |
673 self.cmd_add_entity_type(eschema.type) |
672 self.cmd_add_entity_type(eschema.type) |
674 new.add(eschema.type) |
673 new.add(eschema.type) |
675 # check if attributes has been added to existing entities |
674 # check if attributes has been added to existing entities |
676 for rschema in newcubes_schema.relations(): |
675 for rschema in newcubes_schema.relations(): |
703 # execute pre-remove files |
702 # execute pre-remove files |
704 for cube in reversed(removedcubes): |
703 for cube in reversed(removedcubes): |
705 self.cmd_exec_event_script('preremove', cube) |
704 self.cmd_exec_event_script('preremove', cube) |
706 # remove cubes'entity and relation types |
705 # remove cubes'entity and relation types |
707 for rschema in fsschema.relations(): |
706 for rschema in fsschema.relations(): |
708 if not rschema in removedcubes_schema and rschema in reposchema: |
707 if rschema not in removedcubes_schema and rschema in reposchema: |
709 self.cmd_drop_relation_type(rschema.type) |
708 self.cmd_drop_relation_type(rschema.type) |
710 toremove = [eschema for eschema in fsschema.entities() |
709 toremove = [eschema for eschema in fsschema.entities() |
711 if not eschema in removedcubes_schema |
710 if eschema not in removedcubes_schema and eschema in reposchema] |
712 and eschema in reposchema] |
|
713 for eschema in reversed(order_eschemas(toremove)): |
711 for eschema in reversed(order_eschemas(toremove)): |
714 self.cmd_drop_entity_type(eschema.type) |
712 self.cmd_drop_entity_type(eschema.type) |
715 for rschema in fsschema.relations(): |
713 for rschema in fsschema.relations(): |
716 if rschema in removedcubes_schema and rschema in reposchema: |
714 if rschema in removedcubes_schema and rschema in reposchema: |
717 # check if attributes/relations has been added to entities from |
715 # check if attributes/relations has been added to entities from |
718 # other cubes |
716 # other cubes |
719 for fromtype, totype in rschema.rdefs: |
717 for fromtype, totype in rschema.rdefs: |
720 if (fromtype, totype) not in removedcubes_schema[rschema.type].rdefs and \ |
718 if (fromtype, totype) not in removedcubes_schema[rschema.type].rdefs and \ |
721 (fromtype, totype) in reposchema[rschema.type].rdefs: |
719 (fromtype, totype) in reposchema[rschema.type].rdefs: |
722 self.cmd_drop_relation_definition( |
720 self.cmd_drop_relation_definition( |
723 str(fromtype), rschema.type, str(totype)) |
721 str(fromtype), rschema.type, str(totype)) |
724 # execute post-remove files |
722 # execute post-remove files |
725 for cube in reversed(removedcubes): |
723 for cube in reversed(removedcubes): |
726 self.cmd_exec_event_script('postremove', cube) |
724 self.cmd_exec_event_script('postremove', cube) |
727 self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s', |
725 self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s', |
728 {'pk': u'system.version.'+cube}, ask_confirm=False) |
726 {'pk': u'system.version.' + cube}, ask_confirm=False) |
729 self.commit() |
727 self.commit() |
730 |
728 |
731 # schema migration actions ################################################ |
729 # schema migration actions ################################################ |
732 |
730 |
733 def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True): |
731 def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True): |
766 # skipp NULL values if the attribute is required |
764 # skipp NULL values if the attribute is required |
767 rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname) |
765 rql = 'SET X %s VAL WHERE X is %s, X %s VAL' % (newname, etype, oldname) |
768 card = eschema.rdef(newname).cardinality[0] |
766 card = eschema.rdef(newname).cardinality[0] |
769 if card == '1': |
767 if card == '1': |
770 rql += ', NOT X %s NULL' % oldname |
768 rql += ', NOT X %s NULL' % oldname |
771 self.rqlexec(rql, ask_confirm=self.verbosity>=2) |
769 self.rqlexec(rql, ask_confirm=self.verbosity >= 2) |
772 # XXX if both attributes fulltext indexed, should skip fti rebuild |
770 # XXX if both attributes fulltext indexed, should skip fti rebuild |
773 # XXX if old attribute was fti indexed but not the new one old value |
771 # XXX if old attribute was fti indexed but not the new one old value |
774 # won't be removed from the index (this occurs on other kind of |
772 # won't be removed from the index (this occurs on other kind of |
775 # fulltextindexed change...) |
773 # fulltextindexed change...) |
776 self.cmd_drop_attribute(etype, oldname, commit=commit) |
774 self.cmd_drop_attribute(etype, oldname, commit=commit) |
809 # register entity's attributes |
807 # register entity's attributes |
810 for rschema, attrschema in eschema.attribute_definitions(): |
808 for rschema, attrschema in eschema.attribute_definitions(): |
811 # ignore those meta relations, they will be automatically added |
809 # ignore those meta relations, they will be automatically added |
812 if rschema.type in META_RTYPES: |
810 if rschema.type in META_RTYPES: |
813 continue |
811 continue |
814 if not attrschema.type in instschema: |
812 if attrschema.type not in instschema: |
815 self.cmd_add_entity_type(attrschema.type, False, False) |
813 self.cmd_add_entity_type(attrschema.type, False, False) |
816 if not rschema.type in instschema: |
814 if rschema.type not in instschema: |
817 # need to add the relation type and to commit to get it |
815 # need to add the relation type and to commit to get it |
818 # actually in the schema |
816 # actually in the schema |
819 self.cmd_add_relation_type(rschema.type, False, commit=True) |
817 self.cmd_add_relation_type(rschema.type, False, commit=True) |
820 # register relation definition |
818 # register relation definition |
821 rdef = self._get_rdef(rschema, eschema, eschema.destination(rschema)) |
819 rdef = self._get_rdef(rschema, eschema, eschema.destination(rschema)) |
832 self.rqlexec('SET D specializes P WHERE D eid %(d)s, P name %(pn)s', |
830 self.rqlexec('SET D specializes P WHERE D eid %(d)s, P name %(pn)s', |
833 {'d': instspschema.eid, 'pn': eschema.type}, |
831 {'d': instspschema.eid, 'pn': eschema.type}, |
834 ask_confirm=confirm) |
832 ask_confirm=confirm) |
835 for rschema, tschemas, role in spschema.relation_definitions(True): |
833 for rschema, tschemas, role in spschema.relation_definitions(True): |
836 for tschema in tschemas: |
834 for tschema in tschemas: |
837 if not tschema in instschema: |
835 if tschema not in instschema: |
838 continue |
836 continue |
839 if role == 'subject': |
837 if role == 'subject': |
840 subjschema = spschema |
838 subjschema = spschema |
841 objschema = tschema |
839 objschema = tschema |
842 if rschema.final and rschema in instspschema.subjrels: |
840 if rschema.final and rschema in instspschema.subjrels: |
865 rtypeadded = rschema.type in instschema |
863 rtypeadded = rschema.type in instschema |
866 for targetschema in rschema.objects(etype): |
864 for targetschema in rschema.objects(etype): |
867 # ignore relations where the targeted type is not in the |
865 # ignore relations where the targeted type is not in the |
868 # current instance schema |
866 # current instance schema |
869 targettype = targetschema.type |
867 targettype = targetschema.type |
870 if not targettype in instschema and targettype != etype: |
868 if targettype not in instschema and targettype != etype: |
871 continue |
869 continue |
872 if not rtypeadded: |
870 if not rtypeadded: |
873 # need to add the relation type and to commit to get it |
871 # need to add the relation type and to commit to get it |
874 # actually in the schema |
872 # actually in the schema |
875 added.append(rschema.type) |
873 added.append(rschema.type) |
890 # ignore relations where the targeted type is not in the |
888 # ignore relations where the targeted type is not in the |
891 # current instance schema |
889 # current instance schema |
892 targettype = targetschema.type |
890 targettype = targetschema.type |
893 # don't check targettype != etype since in this case the |
891 # don't check targettype != etype since in this case the |
894 # relation has already been added as a subject relation |
892 # relation has already been added as a subject relation |
895 if not targettype in instschema: |
893 if targettype not in instschema: |
896 continue |
894 continue |
897 if not rtypeadded: |
895 if not rtypeadded: |
898 # need to add the relation type and to commit to get it |
896 # need to add the relation type and to commit to get it |
899 # actually in the schema |
897 # actually in the schema |
900 self.cmd_add_relation_type(rschema.type, False, commit=True) |
898 self.cmd_add_relation_type(rschema.type, False, commit=True) |
916 any hooks called. |
914 any hooks called. |
917 """ |
915 """ |
918 # XXX what if we delete an entity type which is specialized by other types |
916 # XXX what if we delete an entity type which is specialized by other types |
919 # unregister the entity from CWEType |
917 # unregister the entity from CWEType |
920 self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype}, |
918 self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype}, |
921 ask_confirm=self.verbosity>=2) |
919 ask_confirm=self.verbosity >= 2) |
922 if commit: |
920 if commit: |
923 self.commit() |
921 self.commit() |
924 |
922 |
925 def cmd_rename_entity_type(self, oldname, newname, attrs=None, commit=True): |
923 def cmd_rename_entity_type(self, oldname, newname, attrs=None, commit=True): |
926 """rename an existing entity type in the persistent schema |
924 """rename an existing entity type in the persistent schema |
933 print('warning: entity type %s is unknown, skip renaming' % oldname) |
931 print('warning: entity type %s is unknown, skip renaming' % oldname) |
934 return |
932 return |
935 # if merging two existing entity types |
933 # if merging two existing entity types |
936 if newname in schema: |
934 if newname in schema: |
937 assert oldname in ETYPE_NAME_MAP, \ |
935 assert oldname in ETYPE_NAME_MAP, \ |
938 '%s should be mapped to %s in ETYPE_NAME_MAP' % (oldname, |
936 '%s should be mapped to %s in ETYPE_NAME_MAP' % (oldname, newname) |
939 newname) |
|
940 if attrs is None: |
937 if attrs is None: |
941 attrs = ','.join(SQL_PREFIX + rschema.type |
938 attrs = ','.join(SQL_PREFIX + rschema.type |
942 for rschema in schema[newname].subject_relations() |
939 for rschema in schema[newname].subject_relations() |
943 if (rschema.final or rschema.inlined) |
940 if (rschema.final or rschema.inlined) |
944 and not rschema in PURE_VIRTUAL_RTYPES) |
941 and rschema not in PURE_VIRTUAL_RTYPES) |
945 else: |
942 else: |
946 attrs += ('eid', 'creation_date', 'modification_date', 'cwuri') |
943 attrs += ('eid', 'creation_date', 'modification_date', 'cwuri') |
947 attrs = ','.join(SQL_PREFIX + attr for attr in attrs) |
944 attrs = ','.join(SQL_PREFIX + attr for attr in attrs) |
948 self.sqlexec('INSERT INTO %s%s(%s) SELECT %s FROM %s%s' % ( |
945 self.sqlexec('INSERT INTO %s%s(%s) SELECT %s FROM %s%s' % ( |
949 SQL_PREFIX, newname, attrs, attrs, SQL_PREFIX, oldname), |
946 SQL_PREFIX, newname, attrs, attrs, SQL_PREFIX, oldname), |
966 self.sqlexec('UPDATE %s_relation SET eid_to=%s WHERE eid_to=%s' |
963 self.sqlexec('UPDATE %s_relation SET eid_to=%s WHERE eid_to=%s' |
967 % (rtype, new.eid, oldeid), ask_confirm=False) |
964 % (rtype, new.eid, oldeid), ask_confirm=False) |
968 # delete relations using SQL to avoid relations content removal |
965 # delete relations using SQL to avoid relations content removal |
969 # triggered by schema synchronization hooks. |
966 # triggered by schema synchronization hooks. |
970 for rdeftype in ('CWRelation', 'CWAttribute'): |
967 for rdeftype in ('CWRelation', 'CWAttribute'): |
971 thispending = set( (eid for eid, in self.sqlexec( |
968 thispending = set((eid for eid, in self.sqlexec( |
972 'SELECT cw_eid FROM cw_%s WHERE cw_from_entity=%%(eid)s OR ' |
969 'SELECT cw_eid FROM cw_%s WHERE cw_from_entity=%%(eid)s OR ' |
973 ' cw_to_entity=%%(eid)s' % rdeftype, |
970 ' cw_to_entity=%%(eid)s' % rdeftype, |
974 {'eid': oldeid}, ask_confirm=False)) ) |
971 {'eid': oldeid}, ask_confirm=False))) |
975 # we should add deleted eids into pending eids else we may |
972 # we should add deleted eids into pending eids else we may |
976 # get some validation error on commit since integrity hooks |
973 # get some validation error on commit since integrity hooks |
977 # may think some required relation is missing... This also ensure |
974 # may think some required relation is missing... This also ensure |
978 # repository caches are properly cleanup |
975 # repository caches are properly cleanup |
979 hook.CleanupDeletedEidsCacheOp.get_instance(self.cnx).union(thispending) |
976 hook.CleanupDeletedEidsCacheOp.get_instance(self.cnx).union(thispending) |
1007 self.rqlexec('DELETE CWEType ET WHERE ET name %(on)s', {'on': oldname}, |
1004 self.rqlexec('DELETE CWEType ET WHERE ET name %(on)s', {'on': oldname}, |
1008 ask_confirm=False) |
1005 ask_confirm=False) |
1009 # elif simply renaming an entity type |
1006 # elif simply renaming an entity type |
1010 else: |
1007 else: |
1011 self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(on)s', |
1008 self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(on)s', |
1012 {'newname' : text_type(newname), 'on' : oldname}, |
1009 {'newname': text_type(newname), 'on': oldname}, |
1013 ask_confirm=False) |
1010 ask_confirm=False) |
1014 if commit: |
1011 if commit: |
1015 self.commit() |
1012 self.commit() |
1016 |
1013 |
1017 def cmd_add_relation_type(self, rtype, addrdef=True, commit=True): |
1014 def cmd_add_relation_type(self, rtype, addrdef=True, commit=True): |
1048 and reposchema.has_entity(obj)): |
1045 and reposchema.has_entity(obj)): |
1049 continue |
1046 continue |
1050 # symmetric relations appears twice |
1047 # symmetric relations appears twice |
1051 if (subj, obj) in done: |
1048 if (subj, obj) in done: |
1052 continue |
1049 continue |
1053 done.add( (subj, obj) ) |
1050 done.add((subj, obj)) |
1054 self.cmd_add_relation_definition(subj, rtype, obj) |
1051 self.cmd_add_relation_definition(subj, rtype, obj) |
1055 if rtype in META_RTYPES: |
1052 if rtype in META_RTYPES: |
1056 # if the relation is in META_RTYPES, ensure we're adding it for |
1053 # if the relation is in META_RTYPES, ensure we're adding it for |
1057 # all entity types *in the persistent schema*, not only those in |
1054 # all entity types *in the persistent schema*, not only those in |
1058 # the fs schema |
1055 # the fs schema |
1059 for etype in self.repo.schema.entities(): |
1056 for etype in self.repo.schema.entities(): |
1060 if not etype in self.fs_schema: |
1057 if etype not in self.fs_schema: |
1061 # get sample object type and rproperties |
1058 # get sample object type and rproperties |
1062 objtypes = rschema.objects() |
1059 objtypes = rschema.objects() |
1063 assert len(objtypes) == 1, objtypes |
1060 assert len(objtypes) == 1, objtypes |
1064 objtype = objtypes[0] |
1061 objtype = objtypes[0] |
1065 rdef = copy(rschema.rdef(rschema.subjects(objtype)[0], objtype)) |
1062 rdef = copy(rschema.rdef(rschema.subjects(objtype)[0], objtype)) |
1076 |
1073 |
1077 Note that existing relations of the given type will be deleted without |
1074 Note that existing relations of the given type will be deleted without |
1078 any hooks called. |
1075 any hooks called. |
1079 """ |
1076 """ |
1080 self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype, |
1077 self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype, |
1081 ask_confirm=self.verbosity>=2) |
1078 ask_confirm=self.verbosity >= 2) |
1082 self.rqlexec('DELETE CWComputedRType X WHERE X name %r' % rtype, |
1079 self.rqlexec('DELETE CWComputedRType X WHERE X name %r' % rtype, |
1083 ask_confirm=self.verbosity>=2) |
1080 ask_confirm=self.verbosity >= 2) |
1084 if commit: |
1081 if commit: |
1085 self.commit() |
1082 self.commit() |
1086 |
1083 |
1087 def cmd_rename_relation_type(self, oldname, newname, commit=True, force=False): |
1084 def cmd_rename_relation_type(self, oldname, newname, commit=True, force=False): |
1088 """rename an existing relation |
1085 """rename an existing relation |
1098 default='n'): |
1095 default='n'): |
1099 return |
1096 return |
1100 self.cmd_add_relation_type(newname, commit=True) |
1097 self.cmd_add_relation_type(newname, commit=True) |
1101 if not self.repo.schema[oldname].rule: |
1098 if not self.repo.schema[oldname].rule: |
1102 self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname), |
1099 self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname), |
1103 ask_confirm=self.verbosity>=2) |
1100 ask_confirm=self.verbosity >= 2) |
1104 self.cmd_drop_relation_type(oldname, commit=commit) |
1101 self.cmd_drop_relation_type(oldname, commit=commit) |
1105 |
1102 |
1106 def cmd_add_relation_definition(self, subjtype, rtype, objtype, commit=True): |
1103 def cmd_add_relation_definition(self, subjtype, rtype, objtype, commit=True): |
1107 """register a new relation definition, from its definition found in the |
1104 """register a new relation definition, from its definition found in the |
1108 schema definition file |
1105 schema definition file |
1109 """ |
1106 """ |
1110 rschema = self.fs_schema.rschema(rtype) |
1107 rschema = self.fs_schema.rschema(rtype) |
1111 if rschema.rule: |
1108 if rschema.rule: |
1112 raise ExecutionError('Cannot add a relation definition for a ' |
1109 raise ExecutionError('Cannot add a relation definition for a ' |
1113 'computed relation (%s)' % rschema) |
1110 'computed relation (%s)' % rschema) |
1114 if not rtype in self.repo.schema: |
1111 if rtype not in self.repo.schema: |
1115 self.cmd_add_relation_type(rtype, addrdef=False, commit=True) |
1112 self.cmd_add_relation_type(rtype, addrdef=False, commit=True) |
1116 if (subjtype, objtype) in self.repo.schema.rschema(rtype).rdefs: |
1113 if (subjtype, objtype) in self.repo.schema.rschema(rtype).rdefs: |
1117 print('warning: relation %s %s %s is already known, skip addition' % ( |
1114 print('warning: relation %s %s %s is already known, skip addition' % ( |
1118 subjtype, rtype, objtype)) |
1115 subjtype, rtype, objtype)) |
1119 return |
1116 return |
1129 |
1126 |
1130 def _set_rdef_eid(self, rdef): |
1127 def _set_rdef_eid(self, rdef): |
1131 for attr in ('rtype', 'subject', 'object'): |
1128 for attr in ('rtype', 'subject', 'object'): |
1132 schemaobj = getattr(rdef, attr) |
1129 schemaobj = getattr(rdef, attr) |
1133 if getattr(schemaobj, 'eid', None) is None: |
1130 if getattr(schemaobj, 'eid', None) is None: |
1134 schemaobj.eid = self.repo.schema[schemaobj].eid |
1131 schemaobj.eid = self.repo.schema[schemaobj].eid |
1135 assert schemaobj.eid is not None, schemaobj |
1132 assert schemaobj.eid is not None, schemaobj |
1136 return rdef |
1133 return rdef |
1137 |
1134 |
1138 def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True): |
1135 def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True): |
1139 """Drop an existing relation definition. |
1136 """Drop an existing relation definition. |
1151 else: |
1148 else: |
1152 etype = 'CWRelation' |
1149 etype = 'CWRelation' |
1153 rql = ('DELETE %s X WHERE X from_entity FE, FE name "%s",' |
1150 rql = ('DELETE %s X WHERE X from_entity FE, FE name "%s",' |
1154 'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"') |
1151 'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"') |
1155 self.rqlexec(rql % (etype, subjtype, rtype, objtype), |
1152 self.rqlexec(rql % (etype, subjtype, rtype, objtype), |
1156 ask_confirm=self.verbosity>=2) |
1153 ask_confirm=self.verbosity >= 2) |
1157 if commit: |
1154 if commit: |
1158 self.commit() |
1155 self.commit() |
1159 |
1156 |
1160 def cmd_sync_schema_props_perms(self, ertype=None, syncperms=True, |
1157 def cmd_sync_schema_props_perms(self, ertype=None, syncperms=True, |
1161 syncprops=True, syncrdefs=True, commit=True): |
1158 syncprops=True, syncrdefs=True, commit=True): |
1192 syncperms=syncperms, |
1189 syncperms=syncperms, |
1193 syncprops=syncprops) |
1190 syncprops=syncprops) |
1194 else: |
1191 else: |
1195 for etype in self.repo.schema.entities(): |
1192 for etype in self.repo.schema.entities(): |
1196 if etype.eid is None: |
1193 if etype.eid is None: |
1197 # not yet added final etype (thing to BigInt defined in |
1194 # not yet added final etype (thing to BigInt defined in |
1198 # yams though 3.13 migration not done yet) |
1195 # yams though 3.13 migration not done yet) |
1199 continue |
1196 continue |
1200 self._synchronize_eschema(etype, syncrdefs=syncrdefs, |
1197 self._synchronize_eschema(etype, syncrdefs=syncrdefs, |
1201 syncprops=syncprops, syncperms=syncperms) |
1198 syncprops=syncprops, syncperms=syncperms) |
1202 if commit: |
1199 if commit: |
1203 self.commit() |
1200 self.commit() |
1221 for k, v in kwargs.items(): |
1218 for k, v in kwargs.items(): |
1222 values.append('X %s %%(%s)s' % (k, k)) |
1219 values.append('X %s %%(%s)s' % (k, k)) |
1223 if PY2 and isinstance(v, str): |
1220 if PY2 and isinstance(v, str): |
1224 kwargs[k] = unicode(v) |
1221 kwargs[k] = unicode(v) |
1225 rql = 'SET %s WHERE %s' % (','.join(values), ','.join(restriction)) |
1222 rql = 'SET %s WHERE %s' % (','.join(values), ','.join(restriction)) |
1226 self.rqlexec(rql, kwargs, ask_confirm=self.verbosity>=2) |
1223 self.rqlexec(rql, kwargs, ask_confirm=self.verbosity >= 2) |
1227 if commit: |
1224 if commit: |
1228 self.commit() |
1225 self.commit() |
1229 |
1226 |
1230 def cmd_set_size_constraint(self, etype, rtype, size, commit=True): |
1227 def cmd_set_size_constraint(self, etype, rtype, size, commit=True): |
1231 """set change size constraint of a string attribute |
1228 """set change size constraint of a string attribute |
1238 for constr in self.repo.schema.eschema(etype).rdef(rtype).constraints: |
1235 for constr in self.repo.schema.eschema(etype).rdef(rtype).constraints: |
1239 if isinstance(constr, SizeConstraint): |
1236 if isinstance(constr, SizeConstraint): |
1240 oldvalue = constr.max |
1237 oldvalue = constr.max |
1241 if oldvalue == size: |
1238 if oldvalue == size: |
1242 return |
1239 return |
1243 if oldvalue is None and not size is None: |
1240 if oldvalue is None and size is not None: |
1244 ceid = self.rqlexec('INSERT CWConstraint C: C value %(v)s, C cstrtype CT ' |
1241 ceid = self.rqlexec('INSERT CWConstraint C: C value %(v)s, C cstrtype CT ' |
1245 'WHERE CT name "SizeConstraint"', |
1242 'WHERE CT name "SizeConstraint"', |
1246 {'v': SizeConstraint(size).serialize()}, |
1243 {'v': SizeConstraint(size).serialize()}, |
1247 ask_confirm=self.verbosity>=2)[0][0] |
1244 ask_confirm=self.verbosity >= 2)[0][0] |
1248 self.rqlexec('SET X constrained_by C WHERE X from_entity S, X relation_type R, ' |
1245 self.rqlexec('SET X constrained_by C WHERE X from_entity S, X relation_type R, ' |
1249 'S name "%s", R name "%s", C eid %s' % (etype, rtype, ceid), |
1246 'S name "%s", R name "%s", C eid %s' % (etype, rtype, ceid), |
1250 ask_confirm=self.verbosity>=2) |
1247 ask_confirm=self.verbosity >= 2) |
1251 elif not oldvalue is None: |
1248 elif oldvalue is not None: |
1252 if not size is None: |
1249 if size is not None: |
1253 self.rqlexec('SET C value %%(v)s WHERE X from_entity S, X relation_type R,' |
1250 self.rqlexec('SET C value %%(v)s WHERE X from_entity S, X relation_type R,' |
1254 'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",' |
1251 'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",' |
1255 'S name "%s", R name "%s"' % (etype, rtype), |
1252 'S name "%s", R name "%s"' % (etype, rtype), |
1256 {'v': text_type(SizeConstraint(size).serialize())}, |
1253 {'v': text_type(SizeConstraint(size).serialize())}, |
1257 ask_confirm=self.verbosity>=2) |
1254 ask_confirm=self.verbosity >= 2) |
1258 else: |
1255 else: |
1259 self.rqlexec('DELETE X constrained_by C WHERE X from_entity S, X relation_type R,' |
1256 self.rqlexec('DELETE X constrained_by C WHERE X from_entity S, X relation_type R,' |
1260 'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",' |
1257 'X constrained_by C, C cstrtype CT, CT name "SizeConstraint",' |
1261 'S name "%s", R name "%s"' % (etype, rtype), |
1258 'S name "%s", R name "%s"' % (etype, rtype), |
1262 ask_confirm=self.verbosity>=2) |
1259 ask_confirm=self.verbosity >= 2) |
1263 # cleanup unused constraints |
1260 # cleanup unused constraints |
1264 self.rqlexec('DELETE CWConstraint C WHERE NOT X constrained_by C') |
1261 self.rqlexec('DELETE CWConstraint C WHERE NOT X constrained_by C') |
1265 if commit: |
1262 if commit: |
1266 self.commit() |
1263 self.commit() |
1267 |
1264 |
1292 """ |
1289 """ |
1293 wf = self.cmd_create_entity('Workflow', name=text_type(name), |
1290 wf = self.cmd_create_entity('Workflow', name=text_type(name), |
1294 **kwargs) |
1291 **kwargs) |
1295 if not isinstance(wfof, (list, tuple)): |
1292 if not isinstance(wfof, (list, tuple)): |
1296 wfof = (wfof,) |
1293 wfof = (wfof,) |
|
1294 |
1297 def _missing_wf_rel(etype): |
1295 def _missing_wf_rel(etype): |
1298 return 'missing workflow relations, see make_workflowable(%s)' % etype |
1296 return 'missing workflow relations, see make_workflowable(%s)' % etype |
|
1297 |
1299 for etype in wfof: |
1298 for etype in wfof: |
1300 eschema = self.repo.schema[etype] |
1299 eschema = self.repo.schema[etype] |
1301 etype = text_type(etype) |
1300 etype = text_type(etype) |
1302 if ensure_workflowable: |
1301 if ensure_workflowable: |
1303 assert 'in_state' in eschema.subjrels, _missing_wf_rel(etype) |
1302 assert 'in_state' in eschema.subjrels, _missing_wf_rel(etype) |
1470 |
1469 |
1471 def rqlexec(self, rql, kwargs=None, build_descr=True, |
1470 def rqlexec(self, rql, kwargs=None, build_descr=True, |
1472 ask_confirm=False): |
1471 ask_confirm=False): |
1473 """rql action""" |
1472 """rql action""" |
1474 if not isinstance(rql, (tuple, list)): |
1473 if not isinstance(rql, (tuple, list)): |
1475 rql = ( (rql, kwargs), ) |
1474 rql = ((rql, kwargs),) |
1476 res = None |
1475 res = None |
1477 execute = self.cnx.execute |
1476 execute = self.cnx.execute |
1478 for rql, kwargs in rql: |
1477 for rql, kwargs in rql: |
1479 if kwargs: |
1478 if kwargs: |
1480 msg = '%s (%s)' % (rql, kwargs) |
1479 msg = '%s (%s)' % (rql, kwargs) |