194 self.precommit_event() |
209 self.precommit_event() |
195 |
210 |
196 |
211 |
197 class MemSchemaOperation(hook.Operation): |
212 class MemSchemaOperation(hook.Operation): |
198 """base class for schema operations""" |
213 """base class for schema operations""" |
199 def __init__(self, session, kobj=None, **kwargs): |
214 def __init__(self, session, **kwargs): |
200 self.kobj = kobj |
|
201 # once Operation.__init__ has been called, event may be triggered, so |
|
202 # do this last ! |
|
203 hook.Operation.__init__(self, session, **kwargs) |
215 hook.Operation.__init__(self, session, **kwargs) |
204 # every schema operation is triggering a schema update |
216 # every schema operation is triggering a schema update |
205 MemSchemaNotifyChanges(session) |
217 MemSchemaNotifyChanges(session) |
206 |
218 |
207 def prepare_constraints(self, rdef): |
|
208 # if constraints is already a list, reuse it (we're updating multiple |
|
209 # constraints of the same rdef in the same transactions) |
|
210 if not isinstance(rdef.constraints, list): |
|
211 rdef.constraints = list(rdef.constraints) |
|
212 self.constraints = rdef.constraints |
|
213 |
|
214 |
|
215 class MemSchemaEarlyOperation(MemSchemaOperation): |
|
216 def insert_index(self): |
|
217 """schema operation which are inserted at the begining of the queue |
|
218 (typically to add/remove entity or relation types) |
|
219 """ |
|
220 i = -1 |
|
221 for i, op in enumerate(self.session.pending_operations): |
|
222 if not isinstance(op, MemSchemaEarlyOperation): |
|
223 return i |
|
224 return i + 1 |
|
225 |
|
226 |
219 |
227 # operations for high-level source database alteration ######################## |
220 # operations for high-level source database alteration ######################## |
228 |
221 |
229 class SourceDbCWETypeRename(hook.Operation): |
222 class CWETypeAddOp(MemSchemaOperation): |
|
223 """after adding a CWEType entity: |
|
224 * add it to the instance's schema |
|
225 * create the necessary table |
|
226 * set creation_date and modification_date by creating the necessary |
|
227 CWAttribute entities |
|
228 * add owned_by relation by creating the necessary CWRelation entity |
|
229 """ |
|
230 |
|
231 def precommit_event(self): |
|
232 session = self.session |
|
233 entity = self.entity |
|
234 schema = session.vreg.schema |
|
235 etype = ybo.EntityType(eid=entity.eid, name=entity.name, |
|
236 description=entity.description) |
|
237 eschema = schema.add_entity_type(etype) |
|
238 # create the necessary table |
|
239 tablesql = y2sql.eschema2sql(session.pool.source('system').dbhelper, |
|
240 eschema, prefix=SQL_PREFIX) |
|
241 for sql in tablesql.split(';'): |
|
242 if sql.strip(): |
|
243 session.system_sql(sql) |
|
244 # add meta relations |
|
245 gmap = group_mapping(session) |
|
246 cmap = ss.cstrtype_mapping(session) |
|
247 for rtype in (META_RTYPES - VIRTUAL_RTYPES): |
|
248 rschema = schema[rtype] |
|
249 sampletype = rschema.subjects()[0] |
|
250 desttype = rschema.objects()[0] |
|
251 rdef = copy(rschema.rdef(sampletype, desttype)) |
|
252 rdef.subject = mock_object(eid=entity.eid) |
|
253 mock = mock_object(eid=None) |
|
254 ss.execschemarql(session.execute, mock, ss.rdef2rql(rdef, cmap, gmap)) |
|
255 |
|
256 def revertprecommit_event(self): |
|
257 # revert changes on in memory schema |
|
258 self.session.vreg.schema.del_entity_type(self.entity.name) |
|
259 # revert changes on database |
|
260 self.session.system_sql('DROP TABLE %s%s' % (SQL_PREFIX, self.entity.name)) |
|
261 |
|
262 |
|
263 class CWETypeRenameOp(MemSchemaOperation): |
230 """this operation updates physical storage accordingly""" |
264 """this operation updates physical storage accordingly""" |
231 oldname = newname = None # make pylint happy |
265 oldname = newname = None # make pylint happy |
232 |
266 |
233 def precommit_event(self): |
267 def rename(self, oldname, newname): |
|
268 self.session.vreg.schema.rename_entity_type(oldname, newname) |
234 # we need sql to operate physical changes on the system database |
269 # we need sql to operate physical changes on the system database |
235 sqlexec = self.session.system_sql |
270 sqlexec = self.session.system_sql |
236 sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, self.oldname, |
271 sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, oldname, |
237 SQL_PREFIX, self.newname)) |
272 SQL_PREFIX, newname)) |
238 self.info('renamed table %s to %s', self.oldname, self.newname) |
273 self.info('renamed table %s to %s', oldname, newname) |
239 sqlexec('UPDATE entities SET type=%s WHERE type=%s', |
274 sqlexec('UPDATE entities SET type=%s WHERE type=%s', |
240 (self.newname, self.oldname)) |
275 (newname, oldname)) |
241 sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s', |
276 sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s', |
242 (self.newname, self.oldname)) |
277 (newname, oldname)) |
243 |
278 # XXX transaction records |
244 |
279 |
245 class SourceDbCWRTypeUpdate(hook.Operation): |
280 def precommit_event(self): |
|
281 self.rename(self.oldname, self.newname) |
|
282 |
|
283 def revertprecommit_event(self): |
|
284 self.rename(self.newname, self.oldname) |
|
285 |
|
286 |
|
287 class CWRTypeUpdateOp(MemSchemaOperation): |
246 """actually update some properties of a relation definition""" |
288 """actually update some properties of a relation definition""" |
247 rschema = entity = values = None # make pylint happy |
289 rschema = entity = values = None # make pylint happy |
|
290 oldvalus = None |
248 |
291 |
249 def precommit_event(self): |
292 def precommit_event(self): |
250 rschema = self.rschema |
293 rschema = self.rschema |
251 if rschema.final: |
294 if rschema.final: |
252 return |
295 return # watched changes to final relation type are unexpected |
253 session = self.session |
296 session = self.session |
254 if 'fulltext_container' in self.values: |
297 if 'fulltext_container' in self.values: |
255 for subjtype, objtype in rschema.rdefs: |
298 for subjtype, objtype in rschema.rdefs: |
256 hook.set_operation(session, 'fti_update_etypes', subjtype, |
299 hook.set_operation(session, 'fti_update_etypes', subjtype, |
257 UpdateFTIndexOp) |
300 UpdateFTIndexOp) |
258 hook.set_operation(session, 'fti_update_etypes', objtype, |
301 hook.set_operation(session, 'fti_update_etypes', objtype, |
259 UpdateFTIndexOp) |
302 UpdateFTIndexOp) |
|
303 # update the in-memory schema first |
|
304 self.oldvalues = dict( (attr, getattr(rschema, attr)) for attr in self.values) |
|
305 self.rschema.__dict__.update(self.values) |
|
306 # then make necessary changes to the system source database |
260 if not 'inlined' in self.values: |
307 if not 'inlined' in self.values: |
261 return # nothing to do |
308 return # nothing to do |
262 inlined = self.values['inlined'] |
309 inlined = self.values['inlined'] |
263 # check in-lining is necessary / possible |
310 # check in-lining is possible when inlined |
264 if inlined: |
311 if inlined: |
265 self.entity.check_inlined_allowed() |
312 self.entity.check_inlined_allowed() |
266 # inlined changed, make necessary physical changes! |
313 # inlined changed, make necessary physical changes! |
267 sqlexec = self.session.system_sql |
314 sqlexec = self.session.system_sql |
268 rtype = rschema.type |
315 rtype = rschema.type |
429 # modification_date update |
477 # modification_date update |
430 if default: |
478 if default: |
431 session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column), |
479 session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column), |
432 {'default': default}) |
480 {'default': default}) |
433 |
481 |
434 |
482 def revertprecommit_event(self): |
435 class SourceDbCWRelationAdd(SourceDbCWAttributeAdd): |
483 # revert changes on in memory schema |
|
484 self.session.vreg.schema.del_relation_def( |
|
485 self.rdefdef.subject, self.rdefdef.name, self.rdefdef.object) |
|
486 # XXX revert changes on database |
|
487 |
|
488 |
|
489 class CWRelationAddOp(CWAttributeAddOp): |
436 """an actual relation has been added: |
490 """an actual relation has been added: |
437 * if this is an inlined relation, add the necessary column |
491 |
438 else if it's the first instance of this relation type, add the |
492 * add the relation definition to the instance's schema |
439 necessary table and set default permissions |
493 |
440 * register an operation to add the relation definition to the |
494 * if this is an inlined relation, add the necessary column else if it's the |
441 instance's schema on commit |
495 first instance of this relation type, add the necessary table and set |
|
496 default permissions |
442 |
497 |
443 constraints are handled by specific hooks |
498 constraints are handled by specific hooks |
444 """ |
499 """ |
445 entity = None # make pylint happy |
500 entity = None # make pylint happy |
446 |
501 |
447 def precommit_event(self): |
502 def precommit_event(self): |
448 session = self.session |
503 session = self.session |
449 entity = self.entity |
504 entity = self.entity |
450 rdef = self.init_rdef(composite=entity.composite) |
505 # update the in-memory schema first |
|
506 rdefdef = self.init_rdef(composite=entity.composite) |
|
507 # then make necessary changes to the system source database |
451 schema = session.vreg.schema |
508 schema = session.vreg.schema |
452 rtype = rdef.name |
509 rtype = rdefdef.name |
453 rschema = schema.rschema(rtype) |
510 rschema = schema.rschema(rtype) |
454 # this have to be done before permissions setting |
511 # this have to be done before permissions setting |
455 if rschema.inlined: |
512 if rschema.inlined: |
456 # need to add a column if the relation is inlined and if this is the |
513 # need to add a column if the relation is inlined and if this is the |
457 # first occurence of "Subject relation Something" whatever Something |
514 # first occurence of "Subject relation Something" whatever Something |
458 # and if it has not been added during other event of the same |
515 if len(rschema.objects(rdefdef.subject)) == 1: |
459 # transaction |
516 add_inline_relation_column(session, rdefdef.subject, rtype) |
460 key = '%s.%s' % (rdef.subject, rtype) |
|
461 try: |
|
462 alreadythere = bool(rschema.objects(rdef.subject)) |
|
463 except KeyError: |
|
464 alreadythere = False |
|
465 if not (alreadythere or |
|
466 key in session.transaction_data.get('createdattrs', ())): |
|
467 add_inline_relation_column(session, rdef.subject, rtype) |
|
468 else: |
517 else: |
469 # need to create the relation if no relation definition in the |
518 # need to create the relation if no relation definition in the |
470 # schema and if it has not been added during other event of the same |
519 # schema and if it has not been added during other event of the same |
471 # transaction |
520 # transaction |
472 if not (rschema.subjects() or |
521 if not (len(rschema.rdefs) > 1 or |
473 rtype in session.transaction_data.get('createdtables', ())): |
522 rtype in session.transaction_data.get('createdtables', ())): |
474 try: |
523 rschema = schema.rschema(rtype) |
475 rschema = schema.rschema(rtype) |
|
476 tablesql = y2sql.rschema2sql(rschema) |
|
477 except KeyError: |
|
478 # fake we add it to the schema now to get a correctly |
|
479 # initialized schema but remove it before doing anything |
|
480 # more dangerous... |
|
481 rschema = schema.add_relation_type(rdef) |
|
482 tablesql = y2sql.rschema2sql(rschema) |
|
483 schema.del_relation_type(rtype) |
|
484 # create the necessary table |
524 # create the necessary table |
485 for sql in tablesql.split(';'): |
525 for sql in y2sql.rschema2sql(rschema).split(';'): |
486 if sql.strip(): |
526 if sql.strip(): |
487 session.system_sql(sql) |
527 session.system_sql(sql) |
488 session.transaction_data.setdefault('createdtables', []).append( |
528 session.transaction_data.setdefault('createdtables', []).append( |
489 rtype) |
529 rtype) |
490 |
530 |
491 |
531 # XXX revertprecommit_event |
492 class SourceDbRDefUpdate(hook.Operation): |
532 |
|
533 |
|
534 class RDefDelOp(MemSchemaOperation): |
|
535 """an actual relation has been removed""" |
|
536 rdef = None # make pylint happy |
|
537 |
|
538 def precommit_event(self): |
|
539 session = self.session |
|
540 rdef = self.rdef |
|
541 rschema = rdef.rtype |
|
542 # make necessary changes to the system source database first |
|
543 rdeftype = rschema.final and 'CWAttribute' or 'CWRelation' |
|
544 execute = session.execute |
|
545 rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,' |
|
546 'R eid %%(x)s' % rdeftype, {'x': rschema.eid}) |
|
547 lastrel = rset[0][0] == 0 |
|
548 # we have to update physical schema systematically for final and inlined |
|
549 # relations, but only if it's the last instance for this relation type |
|
550 # for other relations |
|
551 if (rschema.final or rschema.inlined): |
|
552 rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, ' |
|
553 'R eid %%(r)s, X from_entity E, E eid %%(e)s' |
|
554 % rdeftype, |
|
555 {'r': rschema.eid, 'e': rdef.subject.eid}) |
|
556 if rset[0][0] == 0 and not session.deleted_in_transaction(rdef.subject.eid): |
|
557 ptypes = session.transaction_data.setdefault('pendingrtypes', set()) |
|
558 ptypes.add(rschema.type) |
|
559 DropColumn(session, table=SQL_PREFIX + str(rdef.subject), |
|
560 column=SQL_PREFIX + str(rschema)) |
|
561 elif lastrel: |
|
562 DropRelationTable(session, str(rschema)) |
|
563 # then update the in-memory schema |
|
564 rschema.del_relation_def(rdef.subject, rdef.object) |
|
565 # if this is the last relation definition of this type, drop associated |
|
566 # relation type |
|
567 if lastrel and not session.deleted_in_transaction(rschema.eid): |
|
568 execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rschema.eid}) |
|
569 |
|
570 def revertprecommit_event(self): |
|
571 # revert changes on in memory schema |
|
572 # |
|
573 # Note: add_relation_def takes a RelationDefinition, not a |
|
574 # RelationDefinitionSchema, needs to fake it |
|
575 self.rdef.name = str(self.rdef.rtype) |
|
576 self.session.vreg.schema.add_relation_def(self.rdef) |
|
577 |
|
578 |
|
579 |
|
580 class RDefUpdateOp(MemSchemaOperation): |
493 """actually update some properties of a relation definition""" |
581 """actually update some properties of a relation definition""" |
494 rschema = values = None # make pylint happy |
582 rschema = rdefkey = values = None # make pylint happy |
|
583 oldvalues = None |
|
584 indexed_changed = null_allowed_changed = False |
495 |
585 |
496 def precommit_event(self): |
586 def precommit_event(self): |
497 session = self.session |
587 session = self.session |
498 etype = self.kobj[0] |
588 rdef = self.rdef = self.rschema.rdefs[self.rdefkey] |
499 table = SQL_PREFIX + etype |
589 # update the in-memory schema first |
500 column = SQL_PREFIX + self.rschema.type |
590 self.oldvalues = dict( (attr, getattr(rdef, attr)) for attr in self.values) |
|
591 rdef.update(self.values) |
|
592 # then make necessary changes to the system source database |
|
593 syssource = session.pool.source('system') |
501 if 'indexed' in self.values: |
594 if 'indexed' in self.values: |
502 sysource = session.pool.source('system') |
595 syssource.update_rdef_indexed(session, rdef) |
503 if self.values['indexed']: |
596 self.indexed_changed = True |
504 sysource.create_index(session, table, column) |
597 if 'cardinality' in self.values and (rdef.rtype.final or |
505 else: |
598 rdef.rtype.inlined) \ |
506 sysource.drop_index(session, table, column) |
599 and self.values['cardinality'][0] != self.oldvalues['cardinality'][0]: |
507 if 'cardinality' in self.values and self.rschema.final: |
600 syssource.update_rdef_null_allowed(self.session, rdef) |
508 syssource = session.pool.source('system') |
601 self.null_allowed_changed = True |
509 if not syssource.dbhelper.alter_column_support: |
|
510 # not supported (and NOT NULL not set by yams in that case, so |
|
511 # no worry) XXX (syt) then should we set NOT NULL below ?? |
|
512 return |
|
513 atype = self.rschema.objects(etype)[0] |
|
514 constraints = self.rschema.rdef(etype, atype).constraints |
|
515 coltype = y2sql.type_from_constraints(syssource.dbhelper, atype, constraints, |
|
516 creating=False) |
|
517 # XXX check self.values['cardinality'][0] actually changed? |
|
518 syssource.set_null_allowed(self.session, table, column, coltype, |
|
519 self.values['cardinality'][0] != '1') |
|
520 if 'fulltextindexed' in self.values: |
602 if 'fulltextindexed' in self.values: |
521 hook.set_operation(session, 'fti_update_etypes', etype, |
603 hook.set_operation(session, 'fti_update_etypes', rdef.subject, |
522 UpdateFTIndexOp) |
604 UpdateFTIndexOp) |
523 |
605 |
524 |
606 def revertprecommit_event(self): |
525 class SourceDbCWConstraintAdd(hook.Operation): |
607 # revert changes on in memory schema |
|
608 self.rdef.update(self.oldvalues) |
|
609 # revert changes on database |
|
610 syssource = self.session.pool.source('system') |
|
611 if self.indexed_changed: |
|
612 syssource.update_rdef_indexed(self.session, self.rdef) |
|
613 if self.null_allowed_changed: |
|
614 syssource.update_rdef_null_allowed(self.session, self.rdef) |
|
615 |
|
616 |
|
617 def _set_modifiable_constraints(rdef): |
|
618 # for proper in-place modification of in-memory schema: if rdef.constraints |
|
619 # is already a list, reuse it (we're updating multiple constraints of the |
|
620 # same rdef in the same transactions) |
|
621 if not isinstance(rdef.constraints, list): |
|
622 rdef.constraints = list(rdef.constraints) |
|
623 |
|
624 |
|
625 class CWConstraintDelOp(MemSchemaOperation): |
|
626 """actually remove a constraint of a relation definition""" |
|
627 rdef = oldcstr = newcstr = None # make pylint happy |
|
628 size_cstr_changed = unique_changed = False |
|
629 |
|
630 def precommit_event(self): |
|
631 session = self.session |
|
632 rdef = self.rdef |
|
633 # in-place modification of in-memory schema first |
|
634 _set_modifiable_constraints(rdef) |
|
635 rdef.constraints.remove(self.oldcstr) |
|
636 # then update database: alter the physical schema on size/unique |
|
637 # constraint changes |
|
638 syssource = session.pool.source('system') |
|
639 cstrtype = self.oldcstr.type() |
|
640 if cstrtype == 'SizeConstraint': |
|
641 syssource.update_rdef_column(session, rdef) |
|
642 self.size_cstr_changed = True |
|
643 elif cstrtype == 'UniqueConstraint': |
|
644 syssource.update_rdef_unique(session, rdef) |
|
645 self.unique_changed = True |
|
646 |
|
647 def revertprecommit_event(self): |
|
648 # revert changes on in memory schema |
|
649 if self.newcstr is not None: |
|
650 self.rdef.constraints.remove(self.newcstr) |
|
651 if self.oldcstr is not None: |
|
652 self.rdef.constraints.append(self.oldcstr) |
|
653 # revert changes on database |
|
654 syssource = self.session.pool.source('system') |
|
655 if self.size_cstr_changed: |
|
656 syssource.update_rdef_column(self.session, self.rdef) |
|
657 if self.unique_changed: |
|
658 syssource.update_rdef_unique(self.session, self.rdef) |
|
659 |
|
660 |
|
661 class CWConstraintAddOp(CWConstraintDelOp): |
526 """actually update constraint of a relation definition""" |
662 """actually update constraint of a relation definition""" |
527 entity = None # make pylint happy |
663 entity = None # make pylint happy |
528 cancelled = False |
664 |
529 |
665 def precommit_event(self): |
530 def precommit_event(self): |
|
531 rdef = self.entity.reverse_constrained_by[0] |
|
532 session = self.session |
666 session = self.session |
|
667 rdefentity = self.entity.reverse_constrained_by[0] |
533 # when the relation is added in the same transaction, the constraint |
668 # when the relation is added in the same transaction, the constraint |
534 # object is created by the operation adding the attribute or relation, |
669 # object is created by the operation adding the attribute or relation, |
535 # so there is nothing to do here |
670 # so there is nothing to do here |
536 if session.added_in_transaction(rdef.eid): |
671 if session.added_in_transaction(rdefentity.eid): |
537 return |
672 return |
538 rdefschema = session.vreg.schema.schema_by_eid(rdef.eid) |
673 rdef = self.rdef = session.vreg.schema.schema_by_eid(rdefentity.eid) |
539 subjtype, rtype, objtype = rdefschema.as_triple() |
|
540 cstrtype = self.entity.type |
674 cstrtype = self.entity.type |
541 oldcstr = rtype.rdef(subjtype, objtype).constraint_by_type(cstrtype) |
675 oldcstr = self.oldcstr = rdef.constraint_by_type(cstrtype) |
542 newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value) |
676 newcstr = self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value) |
543 table = SQL_PREFIX + str(subjtype) |
677 # in-place modification of in-memory schema first |
544 column = SQL_PREFIX + str(rtype) |
678 _set_modifiable_constraints(rdef) |
545 # alter the physical schema on size constraint changes |
679 newcstr.eid = self.entity.eid |
546 if newcstr.type() == 'SizeConstraint' and ( |
680 if oldcstr is not None: |
547 oldcstr is None or oldcstr.max != newcstr.max): |
681 rdef.constraints.remove(oldcstr) |
548 syssource = self.session.pool.source('system') |
682 rdef.constraints.append(newcstr) |
549 card = rtype.rdef(subjtype, objtype).cardinality |
683 # then update database: alter the physical schema on size/unique |
550 coltype = y2sql.type_from_constraints(syssource.dbhelper, objtype, |
684 # constraint changes |
551 [newcstr], creating=False) |
685 syssource = session.pool.source('system') |
552 try: |
686 if cstrtype == 'SizeConstraint' and (oldcstr is None or |
553 syssource.change_col_type(session, table, column, coltype, card[0] != '1') |
687 oldcstr.max != newcstr.max): |
554 self.info('altered column %s of table %s: now %s', |
688 syssource.update_rdef_column(session, rdef) |
555 column, table, coltype) |
689 self.size_cstr_changed = True |
556 except Exception, ex: |
|
557 # not supported by sqlite for instance |
|
558 self.error('error while altering table %s: %s', table, ex) |
|
559 elif cstrtype == 'UniqueConstraint' and oldcstr is None: |
690 elif cstrtype == 'UniqueConstraint' and oldcstr is None: |
560 session.pool.source('system').create_index( |
691 syssource.update_rdef_unique(session, rdef) |
561 self.session, table, column, unique=True) |
692 self.unique_changed = True |
562 |
|
563 |
|
564 class SourceDbCWConstraintDel(hook.Operation): |
|
565 """actually remove a constraint of a relation definition""" |
|
566 rtype = subjtype = None # make pylint happy |
|
567 |
|
568 def precommit_event(self): |
|
569 cstrtype = self.cstr.type() |
|
570 table = SQL_PREFIX + str(self.rdef.subject) |
|
571 column = SQL_PREFIX + str(self.rdef.rtype) |
|
572 # alter the physical schema on size/unique constraint changes |
|
573 if cstrtype == 'SizeConstraint': |
|
574 syssource = self.session.pool.source('system') |
|
575 coltype = y2sql.type_from_constraints(syssource.dbhelper, |
|
576 self.rdef.object, [], |
|
577 creating=False) |
|
578 try: |
|
579 syssource.change_col_type(session, table, column, coltype, |
|
580 self.rdef.cardinality[0] != '1') |
|
581 self.info('altered column %s of table %s: now %s', |
|
582 column, table, coltype) |
|
583 except Exception, ex: |
|
584 # not supported by sqlite for instance |
|
585 self.error('error while altering table %s: %s', table, ex) |
|
586 elif cstrtype == 'UniqueConstraint': |
|
587 self.session.pool.source('system').drop_index( |
|
588 self.session, table, column, unique=True) |
|
589 |
693 |
590 |
694 |
591 # operations for in-memory schema synchronization ############################# |
695 # operations for in-memory schema synchronization ############################# |
592 |
|
593 class MemSchemaCWETypeAdd(MemSchemaEarlyOperation): |
|
594 """actually add the entity type to the instance's schema""" |
|
595 eid = None # make pylint happy |
|
596 def commit_event(self): |
|
597 self.session.vreg.schema.add_entity_type(self.kobj) |
|
598 |
|
599 |
|
600 class MemSchemaCWETypeRename(MemSchemaOperation): |
|
601 """this operation updates physical storage accordingly""" |
|
602 oldname = newname = None # make pylint happy |
|
603 |
|
604 def commit_event(self): |
|
605 self.session.vreg.schema.rename_entity_type(self.oldname, self.newname) |
|
606 |
|
607 |
696 |
608 class MemSchemaCWETypeDel(MemSchemaOperation): |
697 class MemSchemaCWETypeDel(MemSchemaOperation): |
609 """actually remove the entity type from the instance's schema""" |
698 """actually remove the entity type from the instance's schema""" |
610 def commit_event(self): |
699 def postcommit_event(self): |
|
700 # del_entity_type also removes entity's relations |
|
701 self.session.vreg.schema.del_entity_type(self.etype) |
|
702 |
|
703 |
|
704 class MemSchemaCWRTypeAdd(MemSchemaOperation): |
|
705 """actually add the relation type to the instance's schema""" |
|
706 def precommit_event(self): |
|
707 self.session.vreg.schema.add_relation_type(self.rtypedef) |
|
708 |
|
709 def revertprecommit_event(self): |
|
710 self.session.vreg.schema.del_relation_type(self.rtypedef.name) |
|
711 |
|
712 |
|
713 class MemSchemaCWRTypeDel(MemSchemaOperation): |
|
714 """actually remove the relation type from the instance's schema""" |
|
715 def postcommit_event(self): |
611 try: |
716 try: |
612 # del_entity_type also removes entity's relations |
717 self.session.vreg.schema.del_relation_type(self.rtype) |
613 self.session.vreg.schema.del_entity_type(self.kobj) |
|
614 except KeyError: |
718 except KeyError: |
615 # s/o entity type have already been deleted |
719 # s/o entity type have already been deleted |
616 pass |
720 pass |
617 |
721 |
618 |
722 |
619 class MemSchemaCWRTypeAdd(MemSchemaEarlyOperation): |
|
620 """actually add the relation type to the instance's schema""" |
|
621 eid = None # make pylint happy |
|
622 def commit_event(self): |
|
623 self.session.vreg.schema.add_relation_type(self.kobj) |
|
624 |
|
625 |
|
626 class MemSchemaCWRTypeUpdate(MemSchemaOperation): |
|
627 """actually update some properties of a relation definition""" |
|
628 rschema = values = None # make pylint happy |
|
629 |
|
630 def commit_event(self): |
|
631 # structure should be clean, not need to remove entity's relations |
|
632 # at this point |
|
633 self.rschema.__dict__.update(self.values) |
|
634 |
|
635 |
|
636 class MemSchemaCWRTypeDel(MemSchemaOperation): |
|
637 """actually remove the relation type from the instance's schema""" |
|
638 def commit_event(self): |
|
639 try: |
|
640 self.session.vreg.schema.del_relation_type(self.kobj) |
|
641 except KeyError: |
|
642 # s/o entity type have already been deleted |
|
643 pass |
|
644 |
|
645 |
|
646 class MemSchemaRDefAdd(MemSchemaEarlyOperation): |
|
647 """actually add the attribute relation definition to the instance's |
|
648 schema |
|
649 """ |
|
650 def commit_event(self): |
|
651 self.session.vreg.schema.add_relation_def(self.kobj) |
|
652 |
|
653 |
|
654 class MemSchemaRDefUpdate(MemSchemaOperation): |
|
655 """actually update some properties of a relation definition""" |
|
656 rschema = values = None # make pylint happy |
|
657 |
|
658 def commit_event(self): |
|
659 # structure should be clean, not need to remove entity's relations |
|
660 # at this point |
|
661 self.rschema.rdefs[self.kobj].update(self.values) |
|
662 |
|
663 |
|
664 class MemSchemaRDefDel(MemSchemaOperation): |
|
665 """actually remove the relation definition from the instance's schema""" |
|
666 def commit_event(self): |
|
667 subjtype, rtype, objtype = self.kobj |
|
668 try: |
|
669 self.session.vreg.schema.del_relation_def(subjtype, rtype, objtype) |
|
670 except KeyError: |
|
671 # relation type may have been already deleted |
|
672 pass |
|
673 |
|
674 |
|
675 class MemSchemaCWConstraintAdd(MemSchemaOperation): |
|
676 """actually update constraint of a relation definition |
|
677 |
|
678 has to be called before SourceDbCWConstraintAdd |
|
679 """ |
|
680 cancelled = False |
|
681 |
|
682 def precommit_event(self): |
|
683 rdef = self.entity.reverse_constrained_by[0] |
|
684 # when the relation is added in the same transaction, the constraint |
|
685 # object is created by the operation adding the attribute or relation, |
|
686 # so there is nothing to do here |
|
687 if self.session.added_in_transaction(rdef.eid): |
|
688 self.cancelled = True |
|
689 return |
|
690 rdef = self.session.vreg.schema.schema_by_eid(rdef.eid) |
|
691 self.prepare_constraints(rdef) |
|
692 cstrtype = self.entity.type |
|
693 self.cstr = rdef.constraint_by_type(cstrtype) |
|
694 self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value) |
|
695 self.newcstr.eid = self.entity.eid |
|
696 |
|
697 def commit_event(self): |
|
698 if self.cancelled: |
|
699 return |
|
700 # in-place modification |
|
701 if not self.cstr is None: |
|
702 self.constraints.remove(self.cstr) |
|
703 self.constraints.append(self.newcstr) |
|
704 |
|
705 |
|
706 class MemSchemaCWConstraintDel(MemSchemaOperation): |
|
707 """actually remove a constraint of a relation definition |
|
708 |
|
709 has to be called before SourceDbCWConstraintDel |
|
710 """ |
|
711 rtype = subjtype = objtype = None # make pylint happy |
|
712 def precommit_event(self): |
|
713 self.prepare_constraints(self.rdef) |
|
714 |
|
715 def commit_event(self): |
|
716 self.constraints.remove(self.cstr) |
|
717 |
|
718 |
|
719 class MemSchemaPermissionAdd(MemSchemaOperation): |
723 class MemSchemaPermissionAdd(MemSchemaOperation): |
720 """synchronize schema when a *_permission relation has been added on a group |
724 """synchronize schema when a *_permission relation has been added on a group |
721 """ |
725 """ |
722 |
726 |
723 def commit_event(self): |
727 def precommit_event(self): |
724 """the observed connections pool has been commited""" |
728 """the observed connections pool has been commited""" |
725 try: |
729 try: |
726 erschema = self.session.vreg.schema.schema_by_eid(self.eid) |
730 erschema = self.session.vreg.schema.schema_by_eid(self.eid) |
727 except KeyError: |
731 except KeyError: |
728 # duh, schema not found, log error and skip operation |
732 # duh, schema not found, log error and skip operation |
990 rdef = session.vreg.schema.schema_by_eid(self.eidfrom) |
957 rdef = session.vreg.schema.schema_by_eid(self.eidfrom) |
991 except KeyError: |
958 except KeyError: |
992 self.critical('cant get schema rdef associated to %s', self.eidfrom) |
959 self.critical('cant get schema rdef associated to %s', self.eidfrom) |
993 return |
960 return |
994 subjschema, rschema, objschema = rdef.as_triple() |
961 subjschema, rschema, objschema = rdef.as_triple() |
995 pendings = session.transaction_data.get('pendingeids', ()) |
|
996 pendingrdefs = session.transaction_data.setdefault('pendingrdefs', set()) |
962 pendingrdefs = session.transaction_data.setdefault('pendingrdefs', set()) |
997 # first delete existing relation if necessary |
963 # first delete existing relation if necessary |
998 if rschema.final: |
964 if rschema.final: |
999 rdeftype = 'CWAttribute' |
965 rdeftype = 'CWAttribute' |
1000 pendingrdefs.add((subjschema, rschema)) |
966 pendingrdefs.add((subjschema, rschema)) |
1001 else: |
967 else: |
1002 rdeftype = 'CWRelation' |
968 rdeftype = 'CWRelation' |
1003 pendingrdefs.add((subjschema, rschema, objschema)) |
969 pendingrdefs.add((subjschema, rschema, objschema)) |
1004 if not (subjschema.eid in pendings or objschema.eid in pendings): |
970 if not (session.deleted_in_transaction(subjschema.eid) or |
|
971 session.deleted_in_transaction(objschema.eid)): |
1005 session.execute('DELETE X %s Y WHERE X is %s, Y is %s' |
972 session.execute('DELETE X %s Y WHERE X is %s, Y is %s' |
1006 % (rschema, subjschema, objschema)) |
973 % (rschema, subjschema, objschema)) |
1007 execute = session.execute |
974 RDefDelOp(session, rdef=rdef) |
1008 rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,' |
|
1009 'R eid %%(x)s' % rdeftype, {'x': self.eidto}) |
|
1010 lastrel = rset[0][0] == 0 |
|
1011 # we have to update physical schema systematically for final and inlined |
|
1012 # relations, but only if it's the last instance for this relation type |
|
1013 # for other relations |
|
1014 if (rschema.final or rschema.inlined): |
|
1015 rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, ' |
|
1016 'R eid %%(x)s, X from_entity E, E name %%(name)s' |
|
1017 % rdeftype, {'x': self.eidto, 'name': str(subjschema)}) |
|
1018 if rset[0][0] == 0 and not subjschema.eid in pendings: |
|
1019 ptypes = session.transaction_data.setdefault('pendingrtypes', set()) |
|
1020 ptypes.add(rschema.type) |
|
1021 DropColumn(session, table=SQL_PREFIX + subjschema.type, |
|
1022 column=SQL_PREFIX + rschema.type) |
|
1023 elif lastrel: |
|
1024 DropRelationTable(session, rschema.type) |
|
1025 # if this is the last instance, drop associated relation type |
|
1026 if lastrel and not self.eidto in pendings: |
|
1027 execute('DELETE CWRType X WHERE X eid %(x)s', {'x': self.eidto}) |
|
1028 MemSchemaRDefDel(session, (subjschema, rschema, objschema)) |
|
1029 |
975 |
1030 |
976 |
1031 # CWAttribute / CWRelation hooks ############################################### |
977 # CWAttribute / CWRelation hooks ############################################### |
1032 |
978 |
1033 class AfterAddCWAttributeHook(SyncSchemaHook): |
979 class AfterAddCWAttributeHook(SyncSchemaHook): |
1034 __regid__ = 'syncaddcwattribute' |
980 __regid__ = 'syncaddcwattribute' |
1035 __select__ = SyncSchemaHook.__select__ & is_instance('CWAttribute') |
981 __select__ = SyncSchemaHook.__select__ & is_instance('CWAttribute') |
1036 events = ('after_add_entity',) |
982 events = ('after_add_entity',) |
1037 |
983 |
1038 def __call__(self): |
984 def __call__(self): |
1039 SourceDbCWAttributeAdd(self._cw, entity=self.entity) |
985 CWAttributeAddOp(self._cw, entity=self.entity) |
1040 |
986 |
1041 |
987 |
1042 class AfterAddCWRelationHook(AfterAddCWAttributeHook): |
988 class AfterAddCWRelationHook(AfterAddCWAttributeHook): |
1043 __regid__ = 'syncaddcwrelation' |
989 __regid__ = 'syncaddcwrelation' |
1044 __select__ = SyncSchemaHook.__select__ & is_instance('CWRelation') |
990 __select__ = SyncSchemaHook.__select__ & is_instance('CWRelation') |
1045 |
991 |
1046 def __call__(self): |
992 def __call__(self): |
1047 SourceDbCWRelationAdd(self._cw, entity=self.entity) |
993 CWRelationAddOp(self._cw, entity=self.entity) |
1048 |
994 |
1049 |
995 |
1050 class AfterUpdateCWRDefHook(SyncSchemaHook): |
996 class AfterUpdateCWRDefHook(SyncSchemaHook): |
1051 __regid__ = 'syncaddcwattribute' |
997 __regid__ = 'syncaddcwattribute' |
1052 __select__ = SyncSchemaHook.__select__ & is_instance('CWAttribute', |
998 __select__ = SyncSchemaHook.__select__ & is_instance('CWAttribute', |