38 from cubicweb.server.utils import manager_userpasswd |
38 from cubicweb.server.utils import manager_userpasswd |
39 from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX |
39 from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX |
40 except ImportError: # LAX |
40 except ImportError: # LAX |
41 pass |
41 pass |
42 |
42 |
43 |
43 |
44 class ServerMigrationHelper(MigrationHelper): |
44 class ServerMigrationHelper(MigrationHelper): |
45 """specific migration helper for server side migration scripts, |
45 """specific migration helper for server side migration scripts, |
46 providind actions related to schema/data migration |
46 providind actions related to schema/data migration |
47 """ |
47 """ |
48 |
48 |
65 |
65 |
66 @cached |
66 @cached |
67 def repo_connect(self): |
67 def repo_connect(self): |
68 self.repo = get_repository(method='inmemory', config=self.config) |
68 self.repo = get_repository(method='inmemory', config=self.config) |
69 return self.repo |
69 return self.repo |
70 |
70 |
71 def shutdown(self): |
71 def shutdown(self): |
72 if self.repo is not None: |
72 if self.repo is not None: |
73 self.repo.shutdown() |
73 self.repo.shutdown() |
74 |
74 |
75 def rewrite_vcconfiguration(self): |
75 def rewrite_vcconfiguration(self): |
76 """write current installed versions (of cubicweb software |
76 """write current installed versions (of cubicweb software |
77 and of each used cube) into the database |
77 and of each used cube) into the database |
78 """ |
78 """ |
79 self.cmd_set_property('system.version.cubicweb', self.config.cubicweb_version()) |
79 self.cmd_set_property('system.version.cubicweb', self.config.cubicweb_version()) |
80 for pkg in self.config.cubes(): |
80 for pkg in self.config.cubes(): |
81 pkgversion = self.config.cube_version(pkg) |
81 pkgversion = self.config.cube_version(pkg) |
82 self.cmd_set_property('system.version.%s' % pkg.lower(), pkgversion) |
82 self.cmd_set_property('system.version.%s' % pkg.lower(), pkgversion) |
83 self.commit() |
83 self.commit() |
84 |
84 |
85 def backup_database(self, backupfile=None, askconfirm=True): |
85 def backup_database(self, backupfile=None, askconfirm=True): |
86 config = self.config |
86 config = self.config |
87 source = config.sources()['system'] |
87 source = config.sources()['system'] |
88 helper = get_adv_func_helper(source['db-driver']) |
88 helper = get_adv_func_helper(source['db-driver']) |
89 date = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') |
89 date = datetime.now().strftime('%Y-%m-%d_%H:%M:%S') |
111 else: |
111 else: |
112 from cubicweb.toolsutils import restrict_perms_to_user |
112 from cubicweb.toolsutils import restrict_perms_to_user |
113 print 'database backup:', backupfile |
113 print 'database backup:', backupfile |
114 restrict_perms_to_user(backupfile, self.info) |
114 restrict_perms_to_user(backupfile, self.info) |
115 break |
115 break |
116 |
116 |
117 def restore_database(self, backupfile, drop=True): |
117 def restore_database(self, backupfile, drop=True): |
118 config = self.config |
118 config = self.config |
119 source = config.sources()['system'] |
119 source = config.sources()['system'] |
120 helper = get_adv_func_helper(source['db-driver']) |
120 helper = get_adv_func_helper(source['db-driver']) |
121 app = config.appid |
121 app = config.appid |
137 if answer == 1: # 1: continue, 2: retry |
137 if answer == 1: # 1: continue, 2: retry |
138 break |
138 break |
139 else: |
139 else: |
140 break |
140 break |
141 print 'database restored' |
141 print 'database restored' |
142 |
142 |
143 def migrate(self, vcconf, toupgrade, options): |
143 def migrate(self, vcconf, toupgrade, options): |
144 if not options.fs_only: |
144 if not options.fs_only: |
145 if options.backup_db is None: |
145 if options.backup_db is None: |
146 self.backup_database() |
146 self.backup_database() |
147 elif options.backup_db: |
147 elif options.backup_db: |
148 self.backup_database(askconfirm=False) |
148 self.backup_database(askconfirm=False) |
149 super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options) |
149 super(ServerMigrationHelper, self).migrate(vcconf, toupgrade, options) |
150 |
150 |
151 def process_script(self, migrscript, funcname=None, *args, **kwargs): |
151 def process_script(self, migrscript, funcname=None, *args, **kwargs): |
152 """execute a migration script |
152 """execute a migration script |
153 in interactive mode, display the migration script path, ask for |
153 in interactive mode, display the migration script path, ask for |
154 confirmation and execute it if confirmed |
154 confirmation and execute it if confirmed |
155 """ |
155 """ |
157 if self.execscript_confirm(migrscript): |
157 if self.execscript_confirm(migrscript): |
158 sqlexec(open(migrscript).read(), self.session.system_sql) |
158 sqlexec(open(migrscript).read(), self.session.system_sql) |
159 else: |
159 else: |
160 return super(ServerMigrationHelper, self).process_script( |
160 return super(ServerMigrationHelper, self).process_script( |
161 migrscript, funcname, *args, **kwargs) |
161 migrscript, funcname, *args, **kwargs) |
162 |
162 |
163 @property |
163 @property |
164 def cnx(self): |
164 def cnx(self): |
165 """lazy connection""" |
165 """lazy connection""" |
166 try: |
166 try: |
167 return self._cnx |
167 return self._cnx |
192 return self._cnx |
192 return self._cnx |
193 |
193 |
194 @property |
194 @property |
195 def session(self): |
195 def session(self): |
196 return self.repo._get_session(self.cnx.sessionid) |
196 return self.repo._get_session(self.cnx.sessionid) |
197 |
197 |
198 @property |
198 @property |
199 @cached |
199 @cached |
200 def rqlcursor(self): |
200 def rqlcursor(self): |
201 """lazy rql cursor""" |
201 """lazy rql cursor""" |
202 # should not give session as cnx.cursor(), else we may try to execute |
202 # should not give session as cnx.cursor(), else we may try to execute |
203 # some query while no pool is set on the session (eg on entity attribute |
203 # some query while no pool is set on the session (eg on entity attribute |
204 # access for instance) |
204 # access for instance) |
205 return self.cnx.cursor() |
205 return self.cnx.cursor() |
206 |
206 |
207 def commit(self): |
207 def commit(self): |
208 if hasattr(self, '_cnx'): |
208 if hasattr(self, '_cnx'): |
209 self._cnx.commit() |
209 self._cnx.commit() |
210 |
210 |
211 def rollback(self): |
211 def rollback(self): |
212 if hasattr(self, '_cnx'): |
212 if hasattr(self, '_cnx'): |
213 self._cnx.rollback() |
213 self._cnx.rollback() |
214 |
214 |
215 def rqlexecall(self, rqliter, cachekey=None, ask_confirm=True): |
215 def rqlexecall(self, rqliter, cachekey=None, ask_confirm=True): |
216 for rql, kwargs in rqliter: |
216 for rql, kwargs in rqliter: |
217 self.rqlexec(rql, kwargs, cachekey, ask_confirm) |
217 self.rqlexec(rql, kwargs, cachekey, ask_confirm) |
218 |
218 |
219 @cached |
219 @cached |
236 |
236 |
237 @cached |
237 @cached |
238 def group_mapping(self): |
238 def group_mapping(self): |
239 """cached group mapping""" |
239 """cached group mapping""" |
240 return ss.group_mapping(self.rqlcursor) |
240 return ss.group_mapping(self.rqlcursor) |
241 |
241 |
242 def exec_event_script(self, event, cubepath=None, funcname=None, |
242 def exec_event_script(self, event, cubepath=None, funcname=None, |
243 *args, **kwargs): |
243 *args, **kwargs): |
244 if cubepath: |
244 if cubepath: |
245 apc = join(cubepath, 'migration', '%s.py' % event) |
245 apc = join(cubepath, 'migration', '%s.py' % event) |
246 else: |
246 else: |
247 apc = join(self.config.migration_scripts_dir(), '%s.py' % event) |
247 apc = join(self.config.migration_scripts_dir(), '%s.py' % event) |
248 if exists(apc): |
248 if exists(apc): |
263 self.execscript_confirm = execscript_confirm |
263 self.execscript_confirm = execscript_confirm |
264 if self.config.free_wheel: |
264 if self.config.free_wheel: |
265 self.repo.hm.register_hook(setowner_after_add_entity, |
265 self.repo.hm.register_hook(setowner_after_add_entity, |
266 'after_add_entity', '') |
266 'after_add_entity', '') |
267 self.reactivate_verification_hooks() |
267 self.reactivate_verification_hooks() |
268 |
268 |
269 # schema synchronization internals ######################################## |
269 # schema synchronization internals ######################################## |
270 |
270 |
271 def _synchronize_permissions(self, ertype): |
271 def _synchronize_permissions(self, ertype): |
272 """permission synchronization for an entity or relation type""" |
272 """permission synchronization for an entity or relation type""" |
273 if ertype in ('eid', 'has_text', 'identity'): |
273 if ertype in ('eid', 'has_text', 'identity'): |
274 return |
274 return |
275 newrschema = self.fs_schema[ertype] |
275 newrschema = self.fs_schema[ertype] |
330 'X expression %%(expr)s, X mainvars %%(vars)s, T %s X ' |
330 'X expression %%(expr)s, X mainvars %%(vars)s, T %s X ' |
331 'WHERE T eid %%(x)s' % perm, |
331 'WHERE T eid %%(x)s' % perm, |
332 {'expr': expr, 'exprtype': exprtype, |
332 {'expr': expr, 'exprtype': exprtype, |
333 'vars': expression.mainvars, 'x': teid}, 'x', |
333 'vars': expression.mainvars, 'x': teid}, 'x', |
334 ask_confirm=False) |
334 ask_confirm=False) |
335 |
335 |
336 def _synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True): |
336 def _synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True): |
337 """synchronize properties of the persistent relation schema against its |
337 """synchronize properties of the persistent relation schema against its |
338 current definition: |
338 current definition: |
339 |
339 |
340 * description |
340 * description |
341 * symetric, meta |
341 * symetric, meta |
342 * inlined |
342 * inlined |
343 * relation definitions if `syncrdefs` |
343 * relation definitions if `syncrdefs` |
344 * permissions if `syncperms` |
344 * permissions if `syncperms` |
345 |
345 |
346 physical schema changes should be handled by repository's schema hooks |
346 physical schema changes should be handled by repository's schema hooks |
347 """ |
347 """ |
348 rtype = str(rtype) |
348 rtype = str(rtype) |
349 if rtype in self._synchronized: |
349 if rtype in self._synchronized: |
350 return |
350 return |
358 if not reporschema.has_rdef(subj, obj): |
358 if not reporschema.has_rdef(subj, obj): |
359 continue |
359 continue |
360 self._synchronize_rdef_schema(subj, rschema, obj) |
360 self._synchronize_rdef_schema(subj, rschema, obj) |
361 if syncperms: |
361 if syncperms: |
362 self._synchronize_permissions(rtype) |
362 self._synchronize_permissions(rtype) |
363 |
363 |
364 def _synchronize_eschema(self, etype, syncperms=True): |
364 def _synchronize_eschema(self, etype, syncperms=True): |
365 """synchronize properties of the persistent entity schema against |
365 """synchronize properties of the persistent entity schema against |
366 its current definition: |
366 its current definition: |
367 |
367 |
368 * description |
368 * description |
369 * internationalizable, fulltextindexed, indexed, meta |
369 * internationalizable, fulltextindexed, indexed, meta |
370 * relations from/to this entity |
370 * relations from/to this entity |
371 * permissions if `syncperms` |
371 * permissions if `syncperms` |
372 """ |
372 """ |
456 # 2. add new constraints |
456 # 2. add new constraints |
457 for newcstr in newconstraints: |
457 for newcstr in newconstraints: |
458 self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype, |
458 self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype, |
459 newcstr), |
459 newcstr), |
460 ask_confirm=confirm) |
460 ask_confirm=confirm) |
461 |
461 |
462 # base actions ############################################################ |
462 # base actions ############################################################ |
463 |
463 |
464 def checkpoint(self): |
464 def checkpoint(self): |
465 """checkpoint action""" |
465 """checkpoint action""" |
466 if self.confirm('commit now ?', shell=False): |
466 if self.confirm('commit now ?', shell=False): |
467 self.commit() |
467 self.commit() |
468 |
468 |
469 def cmd_add_cube(self, cube, update_database=True): |
469 def cmd_add_cube(self, cube, update_database=True): |
470 self.cmd_add_cubes( (cube,), update_database) |
470 self.cmd_add_cubes( (cube,), update_database) |
471 |
471 |
472 def cmd_add_cubes(self, cubes, update_database=True): |
472 def cmd_add_cubes(self, cubes, update_database=True): |
473 """update_database is telling if the database schema should be updated |
473 """update_database is telling if the database schema should be updated |
474 or if only the relevant eproperty should be inserted (for the case where |
474 or if only the relevant eproperty should be inserted (for the case where |
475 a cube has been extracted from an existing application, so the |
475 a cube has been extracted from an existing application, so the |
476 cube schema is already in there) |
476 cube schema is already in there) |
505 if existingschema.has_rdef(fromtype, totype): |
505 if existingschema.has_rdef(fromtype, totype): |
506 continue |
506 continue |
507 # check we should actually add the relation definition |
507 # check we should actually add the relation definition |
508 if not (fromtype in new or totype in new or rschema in new): |
508 if not (fromtype in new or totype in new or rschema in new): |
509 continue |
509 continue |
510 self.cmd_add_relation_definition(str(fromtype), rschema.type, |
510 self.cmd_add_relation_definition(str(fromtype), rschema.type, |
511 str(totype)) |
511 str(totype)) |
512 # execute post-create files |
512 # execute post-create files |
513 for pack in reversed(newcubes): |
513 for pack in reversed(newcubes): |
514 self.exec_event_script('postcreate', self.config.cube_dir(pack)) |
514 self.exec_event_script('postcreate', self.config.cube_dir(pack)) |
515 self.commit() |
515 self.commit() |
516 |
516 |
517 def cmd_remove_cube(self, cube): |
517 def cmd_remove_cube(self, cube): |
518 removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(cube) |
518 removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(cube) |
519 if not removedcubes: |
519 if not removedcubes: |
520 return |
520 return |
521 fsschema = self.fs_schema |
521 fsschema = self.fs_schema |
530 self.cmd_drop_relation_type(rschema.type) |
530 self.cmd_drop_relation_type(rschema.type) |
531 for eschema in fsschema.entities(): |
531 for eschema in fsschema.entities(): |
532 if not eschema in removedcubes_schema and eschema in reposchema: |
532 if not eschema in removedcubes_schema and eschema in reposchema: |
533 self.cmd_drop_entity_type(eschema.type) |
533 self.cmd_drop_entity_type(eschema.type) |
534 for rschema in fsschema.relations(): |
534 for rschema in fsschema.relations(): |
535 if rschema in removedcubes_schema and rschema in reposchema: |
535 if rschema in removedcubes_schema and rschema in reposchema: |
536 # check if attributes/relations has been added to entities from |
536 # check if attributes/relations has been added to entities from |
537 # other cubes |
537 # other cubes |
538 for fromtype, totype in rschema.iter_rdefs(): |
538 for fromtype, totype in rschema.iter_rdefs(): |
539 if not removedcubes_schema[rschema.type].has_rdef(fromtype, totype) and \ |
539 if not removedcubes_schema[rschema.type].has_rdef(fromtype, totype) and \ |
540 reposchema[rschema.type].has_rdef(fromtype, totype): |
540 reposchema[rschema.type].has_rdef(fromtype, totype): |
541 self.cmd_drop_relation_definition( |
541 self.cmd_drop_relation_definition( |
544 for pack in reversed(removedcubes): |
544 for pack in reversed(removedcubes): |
545 self.exec_event_script('postremove', self.config.cube_dir(pack)) |
545 self.exec_event_script('postremove', self.config.cube_dir(pack)) |
546 self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s', |
546 self.rqlexec('DELETE CWProperty X WHERE X pkey %(pk)s', |
547 {'pk': u'system.version.'+pack}, ask_confirm=False) |
547 {'pk': u'system.version.'+pack}, ask_confirm=False) |
548 self.commit() |
548 self.commit() |
549 |
549 |
550 # schema migration actions ################################################ |
550 # schema migration actions ################################################ |
551 |
551 |
552 def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True): |
552 def cmd_add_attribute(self, etype, attrname, attrtype=None, commit=True): |
553 """add a new attribute on the given entity type""" |
553 """add a new attribute on the given entity type""" |
554 if attrtype is None: |
554 if attrtype is None: |
555 rschema = self.fs_schema.rschema(attrname) |
555 rschema = self.fs_schema.rschema(attrname) |
556 attrtype = rschema.objects(etype)[0] |
556 attrtype = rschema.objects(etype)[0] |
557 self.cmd_add_relation_definition(etype, attrname, attrtype, commit=commit) |
557 self.cmd_add_relation_definition(etype, attrname, attrtype, commit=commit) |
558 |
558 |
559 def cmd_drop_attribute(self, etype, attrname, commit=True): |
559 def cmd_drop_attribute(self, etype, attrname, commit=True): |
560 """drop an existing attribute from the given entity type |
560 """drop an existing attribute from the given entity type |
561 |
561 |
562 `attrname` is a string giving the name of the attribute to drop |
562 `attrname` is a string giving the name of the attribute to drop |
563 """ |
563 """ |
564 rschema = self.repo.schema.rschema(attrname) |
564 rschema = self.repo.schema.rschema(attrname) |
565 attrtype = rschema.objects(etype)[0] |
565 attrtype = rschema.objects(etype)[0] |
566 self.cmd_drop_relation_definition(etype, attrname, attrtype, commit=commit) |
566 self.cmd_drop_relation_definition(etype, attrname, attrtype, commit=commit) |
567 |
567 |
568 def cmd_rename_attribute(self, etype, oldname, newname, commit=True): |
568 def cmd_rename_attribute(self, etype, oldname, newname, commit=True): |
569 """rename an existing attribute of the given entity type |
569 """rename an existing attribute of the given entity type |
570 |
570 |
571 `oldname` is a string giving the name of the existing attribute |
571 `oldname` is a string giving the name of the existing attribute |
572 `newname` is a string giving the name of the renamed attribute |
572 `newname` is a string giving the name of the renamed attribute |
573 """ |
573 """ |
574 eschema = self.fs_schema.eschema(etype) |
574 eschema = self.fs_schema.eschema(etype) |
575 attrtype = eschema.destination(newname) |
575 attrtype = eschema.destination(newname) |
581 card = eschema.rproperty(newname, 'cardinality')[0] |
581 card = eschema.rproperty(newname, 'cardinality')[0] |
582 if card == '1': |
582 if card == '1': |
583 rql += ', NOT X %s NULL' % oldname |
583 rql += ', NOT X %s NULL' % oldname |
584 self.rqlexec(rql, ask_confirm=self.verbosity>=2) |
584 self.rqlexec(rql, ask_confirm=self.verbosity>=2) |
585 self.cmd_drop_attribute(etype, oldname, commit=commit) |
585 self.cmd_drop_attribute(etype, oldname, commit=commit) |
586 |
586 |
587 def cmd_add_entity_type(self, etype, auto=True, commit=True): |
587 def cmd_add_entity_type(self, etype, auto=True, commit=True): |
588 """register a new entity type |
588 """register a new entity type |
589 |
589 |
590 in auto mode, automatically register entity's relation where the |
590 in auto mode, automatically register entity's relation where the |
591 targeted type is known |
591 targeted type is known |
592 """ |
592 """ |
593 applschema = self.repo.schema |
593 applschema = self.repo.schema |
594 if etype in applschema: |
594 if etype in applschema: |
622 self.commit() |
622 self.commit() |
623 added = [] |
623 added = [] |
624 for rschema in eschema.subject_relations(): |
624 for rschema in eschema.subject_relations(): |
625 # attribute relation have already been processed and |
625 # attribute relation have already been processed and |
626 # 'owned_by'/'created_by' will be automatically added |
626 # 'owned_by'/'created_by' will be automatically added |
627 if rschema.final or rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'): |
627 if rschema.final or rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'): |
628 continue |
628 continue |
629 rtypeadded = rschema.type in applschema |
629 rtypeadded = rschema.type in applschema |
630 for targetschema in rschema.objects(etype): |
630 for targetschema in rschema.objects(etype): |
631 # ignore relations where the targeted type is not in the |
631 # ignore relations where the targeted type is not in the |
632 # current application schema |
632 # current application schema |
665 # register relation definition |
665 # register relation definition |
666 self.rqlexecall(ss.rdef2rql(rschema, targettype, etype), |
666 self.rqlexecall(ss.rdef2rql(rschema, targettype, etype), |
667 ask_confirm=confirm) |
667 ask_confirm=confirm) |
668 if commit: |
668 if commit: |
669 self.commit() |
669 self.commit() |
670 |
670 |
671 def cmd_drop_entity_type(self, etype, commit=True): |
671 def cmd_drop_entity_type(self, etype, commit=True): |
672 """unregister an existing entity type |
672 """unregister an existing entity type |
673 |
673 |
674 This will trigger deletion of necessary relation types and definitions |
674 This will trigger deletion of necessary relation types and definitions |
675 """ |
675 """ |
676 # XXX what if we delete an entity type which is specialized by other types |
676 # XXX what if we delete an entity type which is specialized by other types |
677 # unregister the entity from CWEType |
677 # unregister the entity from CWEType |
678 self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype}, |
678 self.rqlexec('DELETE CWEType X WHERE X name %(etype)s', {'etype': etype}, |
680 if commit: |
680 if commit: |
681 self.commit() |
681 self.commit() |
682 |
682 |
683 def cmd_rename_entity_type(self, oldname, newname, commit=True): |
683 def cmd_rename_entity_type(self, oldname, newname, commit=True): |
684 """rename an existing entity type in the persistent schema |
684 """rename an existing entity type in the persistent schema |
685 |
685 |
686 `oldname` is a string giving the name of the existing entity type |
686 `oldname` is a string giving the name of the existing entity type |
687 `newname` is a string giving the name of the renamed entity type |
687 `newname` is a string giving the name of the renamed entity type |
688 """ |
688 """ |
689 self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(oldname)s', |
689 self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(oldname)s', |
690 {'newname' : unicode(newname), 'oldname' : oldname}) |
690 {'newname' : unicode(newname), 'oldname' : oldname}) |
691 if commit: |
691 if commit: |
692 self.commit() |
692 self.commit() |
693 |
693 |
694 def cmd_add_relation_type(self, rtype, addrdef=True, commit=True): |
694 def cmd_add_relation_type(self, rtype, addrdef=True, commit=True): |
695 """register a new relation type named `rtype`, as described in the |
695 """register a new relation type named `rtype`, as described in the |
696 schema description file. |
696 schema description file. |
697 |
697 |
698 `addrdef` is a boolean value; when True, it will also add all relations |
698 `addrdef` is a boolean value; when True, it will also add all relations |
699 of the type just added found in the schema definition file. Note that it |
699 of the type just added found in the schema definition file. Note that it |
700 implies an intermediate "commit" which commits the relation type |
700 implies an intermediate "commit" which commits the relation type |
701 creation (but not the relation definitions themselves, for which |
701 creation (but not the relation definitions themselves, for which |
702 committing depends on the `commit` argument value). |
702 committing depends on the `commit` argument value). |
703 |
703 |
704 """ |
704 """ |
705 rschema = self.fs_schema.rschema(rtype) |
705 rschema = self.fs_schema.rschema(rtype) |
706 # register the relation into CWRType and insert necessary relation |
706 # register the relation into CWRType and insert necessary relation |
707 # definitions |
707 # definitions |
708 self.rqlexecall(ss.rschema2rql(rschema, addrdef=False), |
708 self.rqlexecall(ss.rschema2rql(rschema, addrdef=False), |
714 self.commit() |
714 self.commit() |
715 self.rqlexecall(ss.rdef2rql(rschema), |
715 self.rqlexecall(ss.rdef2rql(rschema), |
716 ask_confirm=self.verbosity>=2) |
716 ask_confirm=self.verbosity>=2) |
717 if commit: |
717 if commit: |
718 self.commit() |
718 self.commit() |
719 |
719 |
720 def cmd_drop_relation_type(self, rtype, commit=True): |
720 def cmd_drop_relation_type(self, rtype, commit=True): |
721 """unregister an existing relation type""" |
721 """unregister an existing relation type""" |
722 # unregister the relation from CWRType |
722 # unregister the relation from CWRType |
723 self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype, |
723 self.rqlexec('DELETE CWRType X WHERE X name %r' % rtype, |
724 ask_confirm=self.verbosity>=2) |
724 ask_confirm=self.verbosity>=2) |
725 if commit: |
725 if commit: |
726 self.commit() |
726 self.commit() |
727 |
727 |
728 def cmd_rename_relation(self, oldname, newname, commit=True): |
728 def cmd_rename_relation(self, oldname, newname, commit=True): |
729 """rename an existing relation |
729 """rename an existing relation |
730 |
730 |
731 `oldname` is a string giving the name of the existing relation |
731 `oldname` is a string giving the name of the existing relation |
732 `newname` is a string giving the name of the renamed relation |
732 `newname` is a string giving the name of the renamed relation |
733 """ |
733 """ |
734 self.cmd_add_relation_type(newname, commit=True) |
734 self.cmd_add_relation_type(newname, commit=True) |
735 self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname), |
735 self.rqlexec('SET X %s Y WHERE X %s Y' % (newname, oldname), |
745 self.cmd_add_relation_type(rtype, addrdef=False, commit=True) |
745 self.cmd_add_relation_type(rtype, addrdef=False, commit=True) |
746 self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype), |
746 self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype), |
747 ask_confirm=self.verbosity>=2) |
747 ask_confirm=self.verbosity>=2) |
748 if commit: |
748 if commit: |
749 self.commit() |
749 self.commit() |
750 |
750 |
751 def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True): |
751 def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True): |
752 """unregister an existing relation definition""" |
752 """unregister an existing relation definition""" |
753 rschema = self.repo.schema.rschema(rtype) |
753 rschema = self.repo.schema.rschema(rtype) |
754 # unregister the definition from CWAttribute or CWRelation |
754 # unregister the definition from CWAttribute or CWRelation |
755 if rschema.is_final(): |
755 if rschema.is_final(): |
760 'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"') |
760 'X relation_type RT, RT name "%s", X to_entity TE, TE name "%s"') |
761 self.rqlexec(rql % (etype, subjtype, rtype, objtype), |
761 self.rqlexec(rql % (etype, subjtype, rtype, objtype), |
762 ask_confirm=self.verbosity>=2) |
762 ask_confirm=self.verbosity>=2) |
763 if commit: |
763 if commit: |
764 self.commit() |
764 self.commit() |
765 |
765 |
766 def cmd_sync_schema_props_perms(self, ertype=None, syncperms=True, |
766 def cmd_sync_schema_props_perms(self, ertype=None, syncperms=True, |
767 syncprops=True, syncrdefs=True, commit=True): |
767 syncprops=True, syncrdefs=True, commit=True): |
768 """synchronize the persistent schema against the current definition |
768 """synchronize the persistent schema against the current definition |
769 schema. |
769 schema. |
770 |
770 |
771 It will synch common stuff between the definition schema and the |
771 It will synch common stuff between the definition schema and the |
772 actual persistent schema, it won't add/remove any entity or relation. |
772 actual persistent schema, it won't add/remove any entity or relation. |
773 """ |
773 """ |
774 assert syncperms or syncprops, 'nothing to do' |
774 assert syncperms or syncprops, 'nothing to do' |
775 if ertype is not None: |
775 if ertype is not None: |
792 self._synchronize_eschema(etype, syncperms=syncperms) |
792 self._synchronize_eschema(etype, syncperms=syncperms) |
793 else: |
793 else: |
794 self._synchronize_permissions(etype) |
794 self._synchronize_permissions(etype) |
795 if commit: |
795 if commit: |
796 self.commit() |
796 self.commit() |
797 |
797 |
798 def cmd_change_relation_props(self, subjtype, rtype, objtype, |
798 def cmd_change_relation_props(self, subjtype, rtype, objtype, |
799 commit=True, **kwargs): |
799 commit=True, **kwargs): |
800 """change some properties of a relation definition |
800 """change some properties of a relation definition |
801 |
801 |
802 you usually want to use sync_schema_props_perms instead. |
802 you usually want to use sync_schema_props_perms instead. |
822 |
822 |
823 def cmd_set_size_constraint(self, etype, rtype, size, commit=True): |
823 def cmd_set_size_constraint(self, etype, rtype, size, commit=True): |
824 """set change size constraint of a string attribute |
824 """set change size constraint of a string attribute |
825 |
825 |
826 if size is None any size constraint will be removed. |
826 if size is None any size constraint will be removed. |
827 |
827 |
828 you usually want to use sync_schema_props_perms instead. |
828 you usually want to use sync_schema_props_perms instead. |
829 """ |
829 """ |
830 oldvalue = None |
830 oldvalue = None |
831 for constr in self.repo.schema.eschema(etype).constraints(rtype): |
831 for constr in self.repo.schema.eschema(etype).constraints(rtype): |
832 if isinstance(constr, SizeConstraint): |
832 if isinstance(constr, SizeConstraint): |
833 oldvalue = constr.max |
833 oldvalue = constr.max |
859 self.commit() |
859 self.commit() |
860 |
860 |
861 @obsolete('use sync_schema_props_perms(ertype, syncprops=False)') |
861 @obsolete('use sync_schema_props_perms(ertype, syncprops=False)') |
862 def cmd_synchronize_permissions(self, ertype, commit=True): |
862 def cmd_synchronize_permissions(self, ertype, commit=True): |
863 self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit) |
863 self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit) |
864 |
864 |
865 # Workflows handling ###################################################### |
865 # Workflows handling ###################################################### |
866 |
866 |
867 def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs): |
867 def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs): |
868 """method to ease workflow definition: add a state for one or more |
868 """method to ease workflow definition: add a state for one or more |
869 entity type(s) |
869 entity type(s) |
870 """ |
870 """ |
871 stateeid = self.cmd_add_entity('State', name=name, **kwargs) |
871 stateeid = self.cmd_add_entity('State', name=name, **kwargs) |
879 self.rqlexec('SET ET initial_state S WHERE ET name %(et)s, S eid %(x)s', |
879 self.rqlexec('SET ET initial_state S WHERE ET name %(et)s, S eid %(x)s', |
880 {'x': stateeid, 'et': etype}, 'x', ask_confirm=False) |
880 {'x': stateeid, 'et': etype}, 'x', ask_confirm=False) |
881 if commit: |
881 if commit: |
882 self.commit() |
882 self.commit() |
883 return stateeid |
883 return stateeid |
884 |
884 |
885 def cmd_add_transition(self, name, transitionof, fromstates, tostate, |
885 def cmd_add_transition(self, name, transitionof, fromstates, tostate, |
886 requiredgroups=(), conditions=(), commit=False, **kwargs): |
886 requiredgroups=(), conditions=(), commit=False, **kwargs): |
887 """method to ease workflow definition: add a transition for one or more |
887 """method to ease workflow definition: add a transition for one or more |
888 entity type(s), from one or more state and to a single state |
888 entity type(s), from one or more state and to a single state |
889 """ |
889 """ |
936 self.session.set_pool() # ensure pool is set |
936 self.session.set_pool() # ensure pool is set |
937 entity = self.session.eid_rset(eid).get_entity(0, 0) |
937 entity = self.session.eid_rset(eid).get_entity(0, 0) |
938 entity.change_state(entity.wf_state(statename).eid) |
938 entity.change_state(entity.wf_state(statename).eid) |
939 if commit: |
939 if commit: |
940 self.commit() |
940 self.commit() |
941 |
941 |
942 # CWProperty handling ###################################################### |
942 # CWProperty handling ###################################################### |
943 |
943 |
944 def cmd_property_value(self, pkey): |
944 def cmd_property_value(self, pkey): |
945 rql = 'Any V WHERE X is CWProperty, X pkey %(k)s, X value V' |
945 rql = 'Any V WHERE X is CWProperty, X pkey %(k)s, X value V' |
946 rset = self.rqlexec(rql, {'k': pkey}, ask_confirm=False) |
946 rset = self.rqlexec(rql, {'k': pkey}, ask_confirm=False) |
956 else: |
956 else: |
957 self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s', |
957 self.rqlexec('SET X value %(v)s WHERE X pkey %(k)s', |
958 {'k': pkey, 'v': value}, ask_confirm=False) |
958 {'k': pkey, 'v': value}, ask_confirm=False) |
959 |
959 |
960 # other data migration commands ########################################### |
960 # other data migration commands ########################################### |
961 |
961 |
962 def cmd_add_entity(self, etype, *args, **kwargs): |
962 def cmd_add_entity(self, etype, *args, **kwargs): |
963 """add a new entity of the given type""" |
963 """add a new entity of the given type""" |
964 rql = 'INSERT %s X' % etype |
964 rql = 'INSERT %s X' % etype |
965 relations = [] |
965 relations = [] |
966 restrictions = [] |
966 restrictions = [] |
976 rql = '%s WHERE %s' % (rql, ', '.join(restrictions)) |
976 rql = '%s WHERE %s' % (rql, ', '.join(restrictions)) |
977 eid = self.rqlexec(rql, kwargs, ask_confirm=self.verbosity>=2).rows[0][0] |
977 eid = self.rqlexec(rql, kwargs, ask_confirm=self.verbosity>=2).rows[0][0] |
978 if commit: |
978 if commit: |
979 self.commit() |
979 self.commit() |
980 return eid |
980 return eid |
981 |
981 |
982 def sqlexec(self, sql, args=None, ask_confirm=True): |
982 def sqlexec(self, sql, args=None, ask_confirm=True): |
983 """execute the given sql if confirmed |
983 """execute the given sql if confirmed |
984 |
984 |
985 should only be used for low level stuff undoable with existing higher |
985 should only be used for low level stuff undoable with existing higher |
986 level actions |
986 level actions |
987 """ |
987 """ |
988 if not ask_confirm or self.confirm('execute sql: %s ?' % sql): |
988 if not ask_confirm or self.confirm('execute sql: %s ?' % sql): |
989 self.session.set_pool() # ensure pool is set |
989 self.session.set_pool() # ensure pool is set |
997 try: |
997 try: |
998 return cu.fetchall() |
998 return cu.fetchall() |
999 except: |
999 except: |
1000 # no result to fetch |
1000 # no result to fetch |
1001 return |
1001 return |
1002 |
1002 |
1003 def rqlexec(self, rql, kwargs=None, cachekey=None, ask_confirm=True): |
1003 def rqlexec(self, rql, kwargs=None, cachekey=None, ask_confirm=True): |
1004 """rql action""" |
1004 """rql action""" |
1005 if not isinstance(rql, (tuple, list)): |
1005 if not isinstance(rql, (tuple, list)): |
1006 rql = ( (rql, kwargs), ) |
1006 rql = ( (rql, kwargs), ) |
1007 res = None |
1007 res = None |
1024 def cmd_deactivate_verification_hooks(self): |
1024 def cmd_deactivate_verification_hooks(self): |
1025 self.repo.hm.deactivate_verification_hooks() |
1025 self.repo.hm.deactivate_verification_hooks() |
1026 |
1026 |
1027 def cmd_reactivate_verification_hooks(self): |
1027 def cmd_reactivate_verification_hooks(self): |
1028 self.repo.hm.reactivate_verification_hooks() |
1028 self.repo.hm.reactivate_verification_hooks() |
1029 |
1029 |
1030 # broken db commands ###################################################### |
1030 # broken db commands ###################################################### |
1031 |
1031 |
1032 def cmd_change_attribute_type(self, etype, attr, newtype, commit=True): |
1032 def cmd_change_attribute_type(self, etype, attr, newtype, commit=True): |
1033 """low level method to change the type of an entity attribute. This is |
1033 """low level method to change the type of an entity attribute. This is |
1034 a quick hack which has some drawback: |
1034 a quick hack which has some drawback: |
1047 sqltype = dbhelper.TYPE_MAPPING[newtype] |
1047 sqltype = dbhelper.TYPE_MAPPING[newtype] |
1048 sql = 'ALTER TABLE %s ALTER COLUMN %s TYPE %s' % (etype, attr, sqltype) |
1048 sql = 'ALTER TABLE %s ALTER COLUMN %s TYPE %s' % (etype, attr, sqltype) |
1049 self.sqlexec(sql, ask_confirm=False) |
1049 self.sqlexec(sql, ask_confirm=False) |
1050 if commit: |
1050 if commit: |
1051 self.commit() |
1051 self.commit() |
1052 |
1052 |
1053 def cmd_add_entity_type_table(self, etype, commit=True): |
1053 def cmd_add_entity_type_table(self, etype, commit=True): |
1054 """low level method to create the sql table for an existing entity. |
1054 """low level method to create the sql table for an existing entity. |
1055 This may be useful on accidental desync between the repository schema |
1055 This may be useful on accidental desync between the repository schema |
1056 and a sql database |
1056 and a sql database |
1057 """ |
1057 """ |
1074 for sql in tablesql.split(';'): |
1074 for sql in tablesql.split(';'): |
1075 if sql.strip(): |
1075 if sql.strip(): |
1076 self.sqlexec(sql) |
1076 self.sqlexec(sql) |
1077 if commit: |
1077 if commit: |
1078 self.commit() |
1078 self.commit() |
1079 |
1079 |
1080 |
1080 |
1081 class ForRqlIterator: |
1081 class ForRqlIterator: |
1082 """specific rql iterator to make the loop skipable""" |
1082 """specific rql iterator to make the loop skipable""" |
1083 def __init__(self, helper, rql, kwargs, ask_confirm): |
1083 def __init__(self, helper, rql, kwargs, ask_confirm): |
1084 self._h = helper |
1084 self._h = helper |
1085 self.rql = rql |
1085 self.rql = rql |
1086 self.kwargs = kwargs |
1086 self.kwargs = kwargs |
1087 self.ask_confirm = ask_confirm |
1087 self.ask_confirm = ask_confirm |
1088 self._rsetit = None |
1088 self._rsetit = None |
1089 |
1089 |
1090 def __iter__(self): |
1090 def __iter__(self): |
1091 return self |
1091 return self |
1092 |
1092 |
1093 def next(self): |
1093 def next(self): |
1094 if self._rsetit is not None: |
1094 if self._rsetit is not None: |
1095 return self._rsetit.next() |
1095 return self._rsetit.next() |
1096 rql, kwargs = self.rql, self.kwargs |
1096 rql, kwargs = self.rql, self.kwargs |
1097 if kwargs: |
1097 if kwargs: |