193 self.precommit_event() |
223 self.precommit_event() |
194 |
224 |
195 |
225 |
196 class MemSchemaOperation(hook.Operation): |
226 class MemSchemaOperation(hook.Operation): |
197 """base class for schema operations""" |
227 """base class for schema operations""" |
198 def __init__(self, session, kobj=None, **kwargs): |
228 def __init__(self, session, **kwargs): |
199 self.kobj = kobj |
|
200 # once Operation.__init__ has been called, event may be triggered, so |
|
201 # do this last ! |
|
202 hook.Operation.__init__(self, session, **kwargs) |
229 hook.Operation.__init__(self, session, **kwargs) |
203 # every schema operation is triggering a schema update |
230 # every schema operation is triggering a schema update |
204 MemSchemaNotifyChanges(session) |
231 MemSchemaNotifyChanges(session) |
205 |
232 |
206 def prepare_constraints(self, rdef): |
|
207 # if constraints is already a list, reuse it (we're updating multiple |
|
208 # constraints of the same rdef in the same transactions) |
|
209 if not isinstance(rdef.constraints, list): |
|
210 rdef.constraints = list(rdef.constraints) |
|
211 self.constraints = rdef.constraints |
|
212 |
|
213 |
|
214 class MemSchemaEarlyOperation(MemSchemaOperation): |
|
215 def insert_index(self): |
|
216 """schema operation which are inserted at the begining of the queue |
|
217 (typically to add/remove entity or relation types) |
|
218 """ |
|
219 i = -1 |
|
220 for i, op in enumerate(self.session.pending_operations): |
|
221 if not isinstance(op, MemSchemaEarlyOperation): |
|
222 return i |
|
223 return i + 1 |
|
224 |
|
225 |
233 |
226 # operations for high-level source database alteration ######################## |
234 # operations for high-level source database alteration ######################## |
227 |
235 |
228 class SourceDbCWETypeRename(hook.Operation): |
236 class CWETypeAddOp(MemSchemaOperation): |
|
237 """after adding a CWEType entity: |
|
238 * add it to the instance's schema |
|
239 * create the necessary table |
|
240 * set creation_date and modification_date by creating the necessary |
|
241 CWAttribute entities |
|
242 * add owned_by relation by creating the necessary CWRelation entity |
|
243 """ |
|
244 |
|
245 def precommit_event(self): |
|
246 session = self.session |
|
247 entity = self.entity |
|
248 schema = session.vreg.schema |
|
249 etype = ybo.EntityType(eid=entity.eid, name=entity.name, |
|
250 description=entity.description) |
|
251 eschema = schema.add_entity_type(etype) |
|
252 # create the necessary table |
|
253 tablesql = y2sql.eschema2sql(session.pool.source('system').dbhelper, |
|
254 eschema, prefix=SQL_PREFIX) |
|
255 for sql in tablesql.split(';'): |
|
256 if sql.strip(): |
|
257 session.system_sql(sql) |
|
258 # add meta relations |
|
259 gmap = group_mapping(session) |
|
260 cmap = ss.cstrtype_mapping(session) |
|
261 for rtype in (META_RTYPES - VIRTUAL_RTYPES): |
|
262 rschema = schema[rtype] |
|
263 sampletype = rschema.subjects()[0] |
|
264 desttype = rschema.objects()[0] |
|
265 rdef = copy(rschema.rdef(sampletype, desttype)) |
|
266 rdef.subject = mock_object(eid=entity.eid) |
|
267 mock = mock_object(eid=None) |
|
268 ss.execschemarql(session.execute, mock, ss.rdef2rql(rdef, cmap, gmap)) |
|
269 |
|
270 def revertprecommit_event(self): |
|
271 # revert changes on in memory schema |
|
272 self.session.vreg.schema.del_entity_type(self.entity.name) |
|
273 # revert changes on database |
|
274 self.session.system_sql('DROP TABLE %s%s' % (SQL_PREFIX, self.entity.name)) |
|
275 |
|
276 |
|
277 class CWETypeRenameOp(MemSchemaOperation): |
229 """this operation updates physical storage accordingly""" |
278 """this operation updates physical storage accordingly""" |
230 oldname = newname = None # make pylint happy |
279 oldname = newname = None # make pylint happy |
231 |
280 |
232 def precommit_event(self): |
281 def rename(self, oldname, newname): |
|
282 self.session.vreg.schema.rename_entity_type(oldname, newname) |
233 # we need sql to operate physical changes on the system database |
283 # we need sql to operate physical changes on the system database |
234 sqlexec = self.session.system_sql |
284 sqlexec = self.session.system_sql |
235 sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, self.oldname, |
285 sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, oldname, |
236 SQL_PREFIX, self.newname)) |
286 SQL_PREFIX, newname)) |
237 self.info('renamed table %s to %s', self.oldname, self.newname) |
287 self.info('renamed table %s to %s', oldname, newname) |
238 sqlexec('UPDATE entities SET type=%s WHERE type=%s', |
288 sqlexec('UPDATE entities SET type=%s WHERE type=%s', |
239 (self.newname, self.oldname)) |
289 (newname, oldname)) |
240 sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s', |
290 sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s', |
241 (self.newname, self.oldname)) |
291 (newname, oldname)) |
242 |
292 # XXX transaction records |
243 |
293 |
244 class SourceDbCWRTypeUpdate(hook.Operation): |
294 def precommit_event(self): |
|
295 self.rename(self.oldname, self.newname) |
|
296 |
|
297 def revertprecommit_event(self): |
|
298 self.rename(self.newname, self.oldname) |
|
299 |
|
300 |
|
301 class CWRTypeUpdateOp(MemSchemaOperation): |
245 """actually update some properties of a relation definition""" |
302 """actually update some properties of a relation definition""" |
246 rschema = entity = values = None # make pylint happy |
303 rschema = entity = values = None # make pylint happy |
|
304 oldvalus = None |
247 |
305 |
248 def precommit_event(self): |
306 def precommit_event(self): |
249 rschema = self.rschema |
307 rschema = self.rschema |
250 if rschema.final: |
308 if rschema.final: |
251 return |
309 return # watched changes to final relation type are unexpected |
252 session = self.session |
310 session = self.session |
253 if 'fulltext_container' in self.values: |
311 if 'fulltext_container' in self.values: |
254 for subjtype, objtype in rschema.rdefs: |
312 for subjtype, objtype in rschema.rdefs: |
255 hook.set_operation(session, 'fti_update_etypes', subjtype, |
313 hook.set_operation(session, 'fti_update_etypes', subjtype, |
256 UpdateFTIndexOp) |
314 UpdateFTIndexOp) |
257 hook.set_operation(session, 'fti_update_etypes', objtype, |
315 hook.set_operation(session, 'fti_update_etypes', objtype, |
258 UpdateFTIndexOp) |
316 UpdateFTIndexOp) |
|
317 # update the in-memory schema first |
|
318 self.oldvalues = dict( (attr, getattr(rschema, attr)) for attr in self.values) |
|
319 self.rschema.__dict__.update(self.values) |
|
320 # then make necessary changes to the system source database |
259 if not 'inlined' in self.values: |
321 if not 'inlined' in self.values: |
260 return # nothing to do |
322 return # nothing to do |
261 inlined = self.values['inlined'] |
323 inlined = self.values['inlined'] |
262 # check in-lining is necessary / possible |
324 # check in-lining is possible when inlined |
263 if inlined: |
325 if inlined: |
264 self.entity.check_inlined_allowed() |
326 self.entity.check_inlined_allowed() |
265 # inlined changed, make necessary physical changes! |
327 # inlined changed, make necessary physical changes! |
266 sqlexec = self.session.system_sql |
328 sqlexec = self.session.system_sql |
267 rtype = rschema.type |
329 rtype = rschema.type |
387 # the entity's type has just been added or if the column |
450 # the entity's type has just been added or if the column |
388 # has not been previously dropped |
451 # has not been previously dropped |
389 self.error('error while altering table %s: %s', table, ex) |
452 self.error('error while altering table %s: %s', table, ex) |
390 if extra_unique_index or entity.indexed: |
453 if extra_unique_index or entity.indexed: |
391 try: |
454 try: |
392 sysource.create_index(session, table, column, |
455 syssource.create_index(session, table, column, |
393 unique=extra_unique_index) |
456 unique=extra_unique_index) |
394 except Exception, ex: |
457 except Exception, ex: |
395 self.error('error while creating index for %s.%s: %s', |
458 self.error('error while creating index for %s.%s: %s', |
396 table, column, ex) |
459 table, column, ex) |
397 # final relations are not infered, propagate |
460 # final relations are not infered, propagate |
398 schema = session.vreg.schema |
461 schema = session.vreg.schema |
399 try: |
462 try: |
400 eschema = schema.eschema(rdef.subject) |
463 eschema = schema.eschema(rdefdef.subject) |
401 except KeyError: |
464 except KeyError: |
402 return # entity type currently being added |
465 return # entity type currently being added |
403 # propagate attribute to children classes |
466 # propagate attribute to children classes |
404 rschema = schema.rschema(rdef.name) |
467 rschema = schema.rschema(rdefdef.name) |
405 # if relation type has been inserted in the same transaction, its final |
468 # if relation type has been inserted in the same transaction, its final |
406 # attribute is still set to False, so we've to ensure it's False |
469 # attribute is still set to False, so we've to ensure it's False |
407 rschema.final = True |
470 rschema.final = True |
408 # XXX 'infered': True/False, not clear actually |
471 insert_rdef_on_subclasses(session, eschema, rschema, rdefdef, props) |
409 props.update({'constraints': rdef.constraints, |
|
410 'description': rdef.description, |
|
411 'cardinality': rdef.cardinality, |
|
412 'constraints': rdef.constraints, |
|
413 'permissions': rdef.get_permissions(), |
|
414 'order': rdef.order, |
|
415 'infered': False, 'eid': None |
|
416 }) |
|
417 cstrtypemap = ss.cstrtype_mapping(session) |
|
418 groupmap = group_mapping(session) |
|
419 object = schema.eschema(rdef.object) |
|
420 for specialization in eschema.specialized_by(False): |
|
421 if (specialization, rdef.object) in rschema.rdefs: |
|
422 continue |
|
423 sperdef = RelationDefinitionSchema(specialization, rschema, |
|
424 object, props) |
|
425 ss.execschemarql(session.execute, sperdef, |
|
426 ss.rdef2rql(sperdef, cstrtypemap, groupmap)) |
|
427 # set default value, using sql for performance and to avoid |
472 # set default value, using sql for performance and to avoid |
428 # modification_date update |
473 # modification_date update |
429 if default: |
474 if default: |
430 session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column), |
475 session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column), |
431 {'default': default}) |
476 {'default': default}) |
432 |
477 |
433 |
478 def revertprecommit_event(self): |
434 class SourceDbCWRelationAdd(SourceDbCWAttributeAdd): |
479 # revert changes on in memory schema |
|
480 self.session.vreg.schema.del_relation_def( |
|
481 self.rdefdef.subject, self.rdefdef.name, self.rdefdef.object) |
|
482 # XXX revert changes on database |
|
483 |
|
484 |
|
485 class CWRelationAddOp(CWAttributeAddOp): |
435 """an actual relation has been added: |
486 """an actual relation has been added: |
436 * if this is an inlined relation, add the necessary column |
487 |
437 else if it's the first instance of this relation type, add the |
488 * add the relation definition to the instance's schema |
438 necessary table and set default permissions |
489 |
439 * register an operation to add the relation definition to the |
490 * if this is an inlined relation, add the necessary column else if it's the |
440 instance's schema on commit |
491 first instance of this relation type, add the necessary table and set |
|
492 default permissions |
441 |
493 |
442 constraints are handled by specific hooks |
494 constraints are handled by specific hooks |
443 """ |
495 """ |
444 entity = None # make pylint happy |
496 entity = None # make pylint happy |
445 |
497 |
446 def precommit_event(self): |
498 def precommit_event(self): |
447 session = self.session |
499 session = self.session |
448 entity = self.entity |
500 entity = self.entity |
449 rdef = self.init_rdef(composite=entity.composite) |
501 # update the in-memory schema first |
|
502 rdefdef = self.init_rdef(composite=entity.composite) |
|
503 # then make necessary changes to the system source database |
450 schema = session.vreg.schema |
504 schema = session.vreg.schema |
451 rtype = rdef.name |
505 rtype = rdefdef.name |
452 rschema = schema.rschema(rtype) |
506 rschema = schema.rschema(rtype) |
453 # this have to be done before permissions setting |
507 # this have to be done before permissions setting |
454 if rschema.inlined: |
508 if rschema.inlined: |
455 # need to add a column if the relation is inlined and if this is the |
509 # need to add a column if the relation is inlined and if this is the |
456 # first occurence of "Subject relation Something" whatever Something |
510 # first occurence of "Subject relation Something" whatever Something |
457 # and if it has not been added during other event of the same |
511 if len(rschema.objects(rdefdef.subject)) == 1: |
458 # transaction |
512 add_inline_relation_column(session, rdefdef.subject, rtype) |
459 key = '%s.%s' % (rdef.subject, rtype) |
513 eschema = schema[rdefdef.subject] |
460 try: |
514 insert_rdef_on_subclasses(session, eschema, rschema, rdefdef, |
461 alreadythere = bool(rschema.objects(rdef.subject)) |
515 {'composite': entity.composite}) |
462 except KeyError: |
|
463 alreadythere = False |
|
464 if not (alreadythere or |
|
465 key in session.transaction_data.get('createdattrs', ())): |
|
466 add_inline_relation_column(session, rdef.subject, rtype) |
|
467 else: |
516 else: |
|
517 if rschema.symmetric: |
|
518 # for symmetric relations, rdefs will store relation definitions |
|
519 # in both ways (i.e. (subj -> obj) and (obj -> subj)) |
|
520 relation_already_defined = len(rschema.rdefs) > 2 |
|
521 else: |
|
522 relation_already_defined = len(rschema.rdefs) > 1 |
468 # need to create the relation if no relation definition in the |
523 # need to create the relation if no relation definition in the |
469 # schema and if it has not been added during other event of the same |
524 # schema and if it has not been added during other event of the same |
470 # transaction |
525 # transaction |
471 if not (rschema.subjects() or |
526 if not (relation_already_defined or |
472 rtype in session.transaction_data.get('createdtables', ())): |
527 rtype in session.transaction_data.get('createdtables', ())): |
473 try: |
528 rschema = schema.rschema(rtype) |
474 rschema = schema.rschema(rtype) |
|
475 tablesql = y2sql.rschema2sql(rschema) |
|
476 except KeyError: |
|
477 # fake we add it to the schema now to get a correctly |
|
478 # initialized schema but remove it before doing anything |
|
479 # more dangerous... |
|
480 rschema = schema.add_relation_type(rdef) |
|
481 tablesql = y2sql.rschema2sql(rschema) |
|
482 schema.del_relation_type(rtype) |
|
483 # create the necessary table |
529 # create the necessary table |
484 for sql in tablesql.split(';'): |
530 for sql in y2sql.rschema2sql(rschema).split(';'): |
485 if sql.strip(): |
531 if sql.strip(): |
486 session.system_sql(sql) |
532 session.system_sql(sql) |
487 session.transaction_data.setdefault('createdtables', []).append( |
533 session.transaction_data.setdefault('createdtables', []).append( |
488 rtype) |
534 rtype) |
489 |
535 |
490 |
536 # XXX revertprecommit_event |
491 class SourceDbRDefUpdate(hook.Operation): |
537 |
|
538 |
|
539 class RDefDelOp(MemSchemaOperation): |
|
540 """an actual relation has been removed""" |
|
541 rdef = None # make pylint happy |
|
542 |
|
543 def precommit_event(self): |
|
544 session = self.session |
|
545 rdef = self.rdef |
|
546 rschema = rdef.rtype |
|
547 # make necessary changes to the system source database first |
|
548 rdeftype = rschema.final and 'CWAttribute' or 'CWRelation' |
|
549 execute = session.execute |
|
550 rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,' |
|
551 'R eid %%(x)s' % rdeftype, {'x': rschema.eid}) |
|
552 lastrel = rset[0][0] == 0 |
|
553 # we have to update physical schema systematically for final and inlined |
|
554 # relations, but only if it's the last instance for this relation type |
|
555 # for other relations |
|
556 if (rschema.final or rschema.inlined): |
|
557 rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, ' |
|
558 'R eid %%(r)s, X from_entity E, E eid %%(e)s' |
|
559 % rdeftype, |
|
560 {'r': rschema.eid, 'e': rdef.subject.eid}) |
|
561 if rset[0][0] == 0 and not session.deleted_in_transaction(rdef.subject.eid): |
|
562 ptypes = session.transaction_data.setdefault('pendingrtypes', set()) |
|
563 ptypes.add(rschema.type) |
|
564 DropColumn(session, table=SQL_PREFIX + str(rdef.subject), |
|
565 column=SQL_PREFIX + str(rschema)) |
|
566 elif lastrel: |
|
567 DropRelationTable(session, str(rschema)) |
|
568 # then update the in-memory schema |
|
569 rschema.del_relation_def(rdef.subject, rdef.object) |
|
570 # if this is the last relation definition of this type, drop associated |
|
571 # relation type |
|
572 if lastrel and not session.deleted_in_transaction(rschema.eid): |
|
573 execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rschema.eid}) |
|
574 |
|
575 def revertprecommit_event(self): |
|
576 # revert changes on in memory schema |
|
577 # |
|
578 # Note: add_relation_def takes a RelationDefinition, not a |
|
579 # RelationDefinitionSchema, needs to fake it |
|
580 self.rdef.name = str(self.rdef.rtype) |
|
581 self.session.vreg.schema.add_relation_def(self.rdef) |
|
582 |
|
583 |
|
584 |
|
585 class RDefUpdateOp(MemSchemaOperation): |
492 """actually update some properties of a relation definition""" |
586 """actually update some properties of a relation definition""" |
493 rschema = values = None # make pylint happy |
587 rschema = rdefkey = values = None # make pylint happy |
|
588 rdef = oldvalues = None |
|
589 indexed_changed = null_allowed_changed = False |
494 |
590 |
495 def precommit_event(self): |
591 def precommit_event(self): |
496 session = self.session |
592 session = self.session |
497 etype = self.kobj[0] |
593 rdef = self.rdef = self.rschema.rdefs[self.rdefkey] |
498 table = SQL_PREFIX + etype |
594 # update the in-memory schema first |
499 column = SQL_PREFIX + self.rschema.type |
595 self.oldvalues = dict( (attr, getattr(rdef, attr)) for attr in self.values) |
|
596 rdef.update(self.values) |
|
597 # then make necessary changes to the system source database |
|
598 syssource = session.pool.source('system') |
500 if 'indexed' in self.values: |
599 if 'indexed' in self.values: |
501 sysource = session.pool.source('system') |
600 syssource.update_rdef_indexed(session, rdef) |
502 if self.values['indexed']: |
601 self.indexed_changed = True |
503 sysource.create_index(session, table, column) |
602 if 'cardinality' in self.values and (rdef.rtype.final or |
504 else: |
603 rdef.rtype.inlined) \ |
505 sysource.drop_index(session, table, column) |
604 and self.values['cardinality'][0] != self.oldvalues['cardinality'][0]: |
506 if 'cardinality' in self.values and self.rschema.final: |
605 syssource.update_rdef_null_allowed(self.session, rdef) |
507 syssource = session.pool.source('system') |
606 self.null_allowed_changed = True |
508 if not syssource.dbhelper.alter_column_support: |
|
509 # not supported (and NOT NULL not set by yams in that case, so |
|
510 # no worry) XXX (syt) then should we set NOT NULL below ?? |
|
511 return |
|
512 atype = self.rschema.objects(etype)[0] |
|
513 constraints = self.rschema.rdef(etype, atype).constraints |
|
514 coltype = y2sql.type_from_constraints(syssource.dbhelper, atype, constraints, |
|
515 creating=False) |
|
516 # XXX check self.values['cardinality'][0] actually changed? |
|
517 syssource.set_null_allowed(self.session, table, column, coltype, |
|
518 self.values['cardinality'][0] != '1') |
|
519 if 'fulltextindexed' in self.values: |
607 if 'fulltextindexed' in self.values: |
520 hook.set_operation(session, 'fti_update_etypes', etype, |
608 hook.set_operation(session, 'fti_update_etypes', rdef.subject, |
521 UpdateFTIndexOp) |
609 UpdateFTIndexOp) |
522 |
610 |
523 |
611 def revertprecommit_event(self): |
524 class SourceDbCWConstraintAdd(hook.Operation): |
612 if self.rdef is None: |
|
613 return |
|
614 # revert changes on in memory schema |
|
615 self.rdef.update(self.oldvalues) |
|
616 # revert changes on database |
|
617 syssource = self.session.pool.source('system') |
|
618 if self.indexed_changed: |
|
619 syssource.update_rdef_indexed(self.session, self.rdef) |
|
620 if self.null_allowed_changed: |
|
621 syssource.update_rdef_null_allowed(self.session, self.rdef) |
|
622 |
|
623 |
|
624 def _set_modifiable_constraints(rdef): |
|
625 # for proper in-place modification of in-memory schema: if rdef.constraints |
|
626 # is already a list, reuse it (we're updating multiple constraints of the |
|
627 # same rdef in the same transactions) |
|
628 if not isinstance(rdef.constraints, list): |
|
629 rdef.constraints = list(rdef.constraints) |
|
630 |
|
631 |
|
632 class CWConstraintDelOp(MemSchemaOperation): |
|
633 """actually remove a constraint of a relation definition""" |
|
634 rdef = oldcstr = newcstr = None # make pylint happy |
|
635 size_cstr_changed = unique_changed = False |
|
636 |
|
637 def precommit_event(self): |
|
638 session = self.session |
|
639 rdef = self.rdef |
|
640 # in-place modification of in-memory schema first |
|
641 _set_modifiable_constraints(rdef) |
|
642 rdef.constraints.remove(self.oldcstr) |
|
643 # then update database: alter the physical schema on size/unique |
|
644 # constraint changes |
|
645 syssource = session.pool.source('system') |
|
646 cstrtype = self.oldcstr.type() |
|
647 if cstrtype == 'SizeConstraint': |
|
648 syssource.update_rdef_column(session, rdef) |
|
649 self.size_cstr_changed = True |
|
650 elif cstrtype == 'UniqueConstraint': |
|
651 syssource.update_rdef_unique(session, rdef) |
|
652 self.unique_changed = True |
|
653 |
|
654 def revertprecommit_event(self): |
|
655 # revert changes on in memory schema |
|
656 if self.newcstr is not None: |
|
657 self.rdef.constraints.remove(self.newcstr) |
|
658 if self.oldcstr is not None: |
|
659 self.rdef.constraints.append(self.oldcstr) |
|
660 # revert changes on database |
|
661 syssource = self.session.pool.source('system') |
|
662 if self.size_cstr_changed: |
|
663 syssource.update_rdef_column(self.session, self.rdef) |
|
664 if self.unique_changed: |
|
665 syssource.update_rdef_unique(self.session, self.rdef) |
|
666 |
|
667 |
|
668 class CWConstraintAddOp(CWConstraintDelOp): |
525 """actually update constraint of a relation definition""" |
669 """actually update constraint of a relation definition""" |
526 entity = None # make pylint happy |
670 entity = None # make pylint happy |
527 cancelled = False |
671 |
528 |
672 def precommit_event(self): |
529 def precommit_event(self): |
|
530 rdef = self.entity.reverse_constrained_by[0] |
|
531 session = self.session |
673 session = self.session |
|
674 rdefentity = self.entity.reverse_constrained_by[0] |
532 # when the relation is added in the same transaction, the constraint |
675 # when the relation is added in the same transaction, the constraint |
533 # object is created by the operation adding the attribute or relation, |
676 # object is created by the operation adding the attribute or relation, |
534 # so there is nothing to do here |
677 # so there is nothing to do here |
535 if session.added_in_transaction(rdef.eid): |
678 if session.added_in_transaction(rdefentity.eid): |
536 return |
679 return |
537 rdefschema = session.vreg.schema.schema_by_eid(rdef.eid) |
680 rdef = self.rdef = session.vreg.schema.schema_by_eid(rdefentity.eid) |
538 subjtype, rtype, objtype = rdefschema.as_triple() |
|
539 cstrtype = self.entity.type |
681 cstrtype = self.entity.type |
540 oldcstr = rtype.rdef(subjtype, objtype).constraint_by_type(cstrtype) |
682 oldcstr = self.oldcstr = rdef.constraint_by_type(cstrtype) |
541 newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value) |
683 newcstr = self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value) |
542 table = SQL_PREFIX + str(subjtype) |
684 # in-place modification of in-memory schema first |
543 column = SQL_PREFIX + str(rtype) |
685 _set_modifiable_constraints(rdef) |
544 # alter the physical schema on size constraint changes |
686 newcstr.eid = self.entity.eid |
545 if newcstr.type() == 'SizeConstraint' and ( |
687 if oldcstr is not None: |
546 oldcstr is None or oldcstr.max != newcstr.max): |
688 rdef.constraints.remove(oldcstr) |
547 syssource = self.session.pool.source('system') |
689 rdef.constraints.append(newcstr) |
548 card = rtype.rdef(subjtype, objtype).cardinality |
690 # then update database: alter the physical schema on size/unique |
549 coltype = y2sql.type_from_constraints(syssource.dbhelper, objtype, |
691 # constraint changes |
550 [newcstr], creating=False) |
692 syssource = session.pool.source('system') |
551 try: |
693 if cstrtype == 'SizeConstraint' and (oldcstr is None or |
552 syssource.change_col_type(session, table, column, coltype, card[0] != '1') |
694 oldcstr.max != newcstr.max): |
553 self.info('altered column %s of table %s: now %s', |
695 syssource.update_rdef_column(session, rdef) |
554 column, table, coltype) |
696 self.size_cstr_changed = True |
555 except Exception, ex: |
|
556 # not supported by sqlite for instance |
|
557 self.error('error while altering table %s: %s', table, ex) |
|
558 elif cstrtype == 'UniqueConstraint' and oldcstr is None: |
697 elif cstrtype == 'UniqueConstraint' and oldcstr is None: |
559 session.pool.source('system').create_index( |
698 syssource.update_rdef_unique(session, rdef) |
560 self.session, table, column, unique=True) |
699 self.unique_changed = True |
561 |
700 |
562 |
701 class CWUniqueTogetherConstraintAddOp(MemSchemaOperation): |
563 class SourceDbCWConstraintDel(hook.Operation): |
702 entity = None # make pylint happy |
564 """actually remove a constraint of a relation definition""" |
703 def precommit_event(self): |
565 rtype = subjtype = None # make pylint happy |
704 session = self.session |
566 |
705 prefix = SQL_PREFIX |
567 def precommit_event(self): |
706 table = '%s%s' % (prefix, self.entity.constraint_of[0].name) |
568 cstrtype = self.cstr.type() |
707 cols = ['%s%s' % (prefix, r.rtype.name) |
569 table = SQL_PREFIX + str(self.rdef.subject) |
708 for r in self.entity.relations] |
570 column = SQL_PREFIX + str(self.rdef.rtype) |
709 dbhelper= session.pool.source('system').dbhelper |
571 # alter the physical schema on size/unique constraint changes |
710 sqls = dbhelper.sqls_create_multicol_unique_index(table, cols) |
572 if cstrtype == 'SizeConstraint': |
711 for sql in sqls: |
573 syssource = self.session.pool.source('system') |
712 session.system_sql(sql) |
574 coltype = y2sql.type_from_constraints(syssource.dbhelper, |
713 |
575 self.rdef.object, [], |
714 # XXX revertprecommit_event |
576 creating=False) |
715 |
577 try: |
716 def postcommit_event(self): |
578 syssource.change_col_type(session, table, column, coltype, |
717 eschema = self.session.vreg.schema.schema_by_eid(self.entity.constraint_of[0].eid) |
579 self.rdef.cardinality[0] != '1') |
718 attrs = [r.rtype.name for r in self.entity.relations] |
580 self.info('altered column %s of table %s: now %s', |
719 eschema._unique_together.append(attrs) |
581 column, table, coltype) |
720 |
582 except Exception, ex: |
721 class CWUniqueTogetherConstraintDelOp(MemSchemaOperation): |
583 # not supported by sqlite for instance |
722 entity = oldcstr = None # for pylint |
584 self.error('error while altering table %s: %s', table, ex) |
723 cols = [] # for pylint |
585 elif cstrtype == 'UniqueConstraint': |
724 def precommit_event(self): |
586 self.session.pool.source('system').drop_index( |
725 session = self.session |
587 self.session, table, column, unique=True) |
726 prefix = SQL_PREFIX |
588 |
727 table = '%s%s' % (prefix, self.entity.type) |
|
728 dbhelper= session.pool.source('system').dbhelper |
|
729 cols = ['%s%s' % (prefix, c) for c in self.cols] |
|
730 sqls = dbhelper.sqls_drop_multicol_unique_index(table, cols) |
|
731 for sql in sqls: |
|
732 session.system_sql(sql) |
|
733 |
|
734 # XXX revertprecommit_event |
|
735 |
|
736 def postcommit_event(self): |
|
737 eschema = self.session.vreg.schema.schema_by_eid(self.entity.eid) |
|
738 cols = set(self.cols) |
|
739 unique_together = [ut for ut in eschema._unique_together |
|
740 if set(ut) != cols] |
|
741 eschema._unique_together = unique_together |
589 |
742 |
590 # operations for in-memory schema synchronization ############################# |
743 # operations for in-memory schema synchronization ############################# |
591 |
|
592 class MemSchemaCWETypeAdd(MemSchemaEarlyOperation): |
|
593 """actually add the entity type to the instance's schema""" |
|
594 eid = None # make pylint happy |
|
595 def commit_event(self): |
|
596 self.session.vreg.schema.add_entity_type(self.kobj) |
|
597 |
|
598 |
|
599 class MemSchemaCWETypeRename(MemSchemaOperation): |
|
600 """this operation updates physical storage accordingly""" |
|
601 oldname = newname = None # make pylint happy |
|
602 |
|
603 def commit_event(self): |
|
604 self.session.vreg.schema.rename_entity_type(self.oldname, self.newname) |
|
605 |
|
606 |
744 |
607 class MemSchemaCWETypeDel(MemSchemaOperation): |
745 class MemSchemaCWETypeDel(MemSchemaOperation): |
608 """actually remove the entity type from the instance's schema""" |
746 """actually remove the entity type from the instance's schema""" |
609 def commit_event(self): |
747 def postcommit_event(self): |
|
748 # del_entity_type also removes entity's relations |
|
749 self.session.vreg.schema.del_entity_type(self.etype) |
|
750 |
|
751 |
|
752 class MemSchemaCWRTypeAdd(MemSchemaOperation): |
|
753 """actually add the relation type to the instance's schema""" |
|
754 def precommit_event(self): |
|
755 self.session.vreg.schema.add_relation_type(self.rtypedef) |
|
756 |
|
757 def revertprecommit_event(self): |
|
758 self.session.vreg.schema.del_relation_type(self.rtypedef.name) |
|
759 |
|
760 |
|
761 class MemSchemaCWRTypeDel(MemSchemaOperation): |
|
762 """actually remove the relation type from the instance's schema""" |
|
763 def postcommit_event(self): |
610 try: |
764 try: |
611 # del_entity_type also removes entity's relations |
765 self.session.vreg.schema.del_relation_type(self.rtype) |
612 self.session.vreg.schema.del_entity_type(self.kobj) |
|
613 except KeyError: |
766 except KeyError: |
614 # s/o entity type have already been deleted |
767 # s/o entity type have already been deleted |
615 pass |
768 pass |
616 |
769 |
617 |
770 |
618 class MemSchemaCWRTypeAdd(MemSchemaEarlyOperation): |
|
619 """actually add the relation type to the instance's schema""" |
|
620 eid = None # make pylint happy |
|
621 def commit_event(self): |
|
622 self.session.vreg.schema.add_relation_type(self.kobj) |
|
623 |
|
624 |
|
625 class MemSchemaCWRTypeUpdate(MemSchemaOperation): |
|
626 """actually update some properties of a relation definition""" |
|
627 rschema = values = None # make pylint happy |
|
628 |
|
629 def commit_event(self): |
|
630 # structure should be clean, not need to remove entity's relations |
|
631 # at this point |
|
632 self.rschema.__dict__.update(self.values) |
|
633 |
|
634 |
|
635 class MemSchemaCWRTypeDel(MemSchemaOperation): |
|
636 """actually remove the relation type from the instance's schema""" |
|
637 def commit_event(self): |
|
638 try: |
|
639 self.session.vreg.schema.del_relation_type(self.kobj) |
|
640 except KeyError: |
|
641 # s/o entity type have already been deleted |
|
642 pass |
|
643 |
|
644 |
|
645 class MemSchemaRDefAdd(MemSchemaEarlyOperation): |
|
646 """actually add the attribute relation definition to the instance's |
|
647 schema |
|
648 """ |
|
649 def commit_event(self): |
|
650 self.session.vreg.schema.add_relation_def(self.kobj) |
|
651 |
|
652 |
|
653 class MemSchemaRDefUpdate(MemSchemaOperation): |
|
654 """actually update some properties of a relation definition""" |
|
655 rschema = values = None # make pylint happy |
|
656 |
|
657 def commit_event(self): |
|
658 # structure should be clean, not need to remove entity's relations |
|
659 # at this point |
|
660 self.rschema.rdefs[self.kobj].update(self.values) |
|
661 |
|
662 |
|
663 class MemSchemaRDefDel(MemSchemaOperation): |
|
664 """actually remove the relation definition from the instance's schema""" |
|
665 def commit_event(self): |
|
666 subjtype, rtype, objtype = self.kobj |
|
667 try: |
|
668 self.session.vreg.schema.del_relation_def(subjtype, rtype, objtype) |
|
669 except KeyError: |
|
670 # relation type may have been already deleted |
|
671 pass |
|
672 |
|
673 |
|
674 class MemSchemaCWConstraintAdd(MemSchemaOperation): |
|
675 """actually update constraint of a relation definition |
|
676 |
|
677 has to be called before SourceDbCWConstraintAdd |
|
678 """ |
|
679 cancelled = False |
|
680 |
|
681 def precommit_event(self): |
|
682 rdef = self.entity.reverse_constrained_by[0] |
|
683 # when the relation is added in the same transaction, the constraint |
|
684 # object is created by the operation adding the attribute or relation, |
|
685 # so there is nothing to do here |
|
686 if self.session.added_in_transaction(rdef.eid): |
|
687 self.cancelled = True |
|
688 return |
|
689 rdef = self.session.vreg.schema.schema_by_eid(rdef.eid) |
|
690 self.prepare_constraints(rdef) |
|
691 cstrtype = self.entity.type |
|
692 self.cstr = rdef.constraint_by_type(cstrtype) |
|
693 self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value) |
|
694 self.newcstr.eid = self.entity.eid |
|
695 |
|
696 def commit_event(self): |
|
697 if self.cancelled: |
|
698 return |
|
699 # in-place modification |
|
700 if not self.cstr is None: |
|
701 self.constraints.remove(self.cstr) |
|
702 self.constraints.append(self.newcstr) |
|
703 |
|
704 |
|
705 class MemSchemaCWConstraintDel(MemSchemaOperation): |
|
706 """actually remove a constraint of a relation definition |
|
707 |
|
708 has to be called before SourceDbCWConstraintDel |
|
709 """ |
|
710 rtype = subjtype = objtype = None # make pylint happy |
|
711 def precommit_event(self): |
|
712 self.prepare_constraints(self.rdef) |
|
713 |
|
714 def commit_event(self): |
|
715 self.constraints.remove(self.cstr) |
|
716 |
|
717 |
|
718 class MemSchemaPermissionAdd(MemSchemaOperation): |
771 class MemSchemaPermissionAdd(MemSchemaOperation): |
719 """synchronize schema when a *_permission relation has been added on a group |
772 """synchronize schema when a *_permission relation has been added on a group |
720 """ |
773 """ |
721 |
774 |
722 def commit_event(self): |
775 def precommit_event(self): |
723 """the observed connections pool has been commited""" |
776 """the observed connections pool has been commited""" |
724 try: |
777 try: |
725 erschema = self.session.vreg.schema.schema_by_eid(self.eid) |
778 erschema = self.session.vreg.schema.schema_by_eid(self.eid) |
726 except KeyError: |
779 except KeyError: |
727 # duh, schema not found, log error and skip operation |
780 # duh, schema not found, log error and skip operation |
982 __select__ = SyncSchemaHook.__select__ & hook.match_rtype('relation_type') |
999 __select__ = SyncSchemaHook.__select__ & hook.match_rtype('relation_type') |
983 events = ('after_delete_relation',) |
1000 events = ('after_delete_relation',) |
984 |
1001 |
985 def __call__(self): |
1002 def __call__(self): |
986 session = self._cw |
1003 session = self._cw |
987 rdef = session.vreg.schema.schema_by_eid(self.eidfrom) |
1004 try: |
|
1005 rdef = session.vreg.schema.schema_by_eid(self.eidfrom) |
|
1006 except KeyError: |
|
1007 self.critical('cant get schema rdef associated to %s', self.eidfrom) |
|
1008 return |
988 subjschema, rschema, objschema = rdef.as_triple() |
1009 subjschema, rschema, objschema = rdef.as_triple() |
989 pendings = session.transaction_data.get('pendingeids', ()) |
|
990 pendingrdefs = session.transaction_data.setdefault('pendingrdefs', set()) |
1010 pendingrdefs = session.transaction_data.setdefault('pendingrdefs', set()) |
991 # first delete existing relation if necessary |
1011 # first delete existing relation if necessary |
992 if rschema.final: |
1012 if rschema.final: |
993 rdeftype = 'CWAttribute' |
1013 rdeftype = 'CWAttribute' |
994 pendingrdefs.add((subjschema, rschema)) |
1014 pendingrdefs.add((subjschema, rschema)) |
995 else: |
1015 else: |
996 rdeftype = 'CWRelation' |
1016 rdeftype = 'CWRelation' |
997 pendingrdefs.add((subjschema, rschema, objschema)) |
1017 pendingrdefs.add((subjschema, rschema, objschema)) |
998 if not (subjschema.eid in pendings or objschema.eid in pendings): |
1018 if not (session.deleted_in_transaction(subjschema.eid) or |
|
1019 session.deleted_in_transaction(objschema.eid)): |
999 session.execute('DELETE X %s Y WHERE X is %s, Y is %s' |
1020 session.execute('DELETE X %s Y WHERE X is %s, Y is %s' |
1000 % (rschema, subjschema, objschema)) |
1021 % (rschema, subjschema, objschema)) |
1001 execute = session.execute |
1022 RDefDelOp(session, rdef=rdef) |
1002 rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,' |
|
1003 'R eid %%(x)s' % rdeftype, {'x': self.eidto}) |
|
1004 lastrel = rset[0][0] == 0 |
|
1005 # we have to update physical schema systematically for final and inlined |
|
1006 # relations, but only if it's the last instance for this relation type |
|
1007 # for other relations |
|
1008 |
|
1009 if (rschema.final or rschema.inlined): |
|
1010 rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, ' |
|
1011 'R eid %%(x)s, X from_entity E, E name %%(name)s' |
|
1012 % rdeftype, {'x': self.eidto, 'name': str(subjschema)}) |
|
1013 if rset[0][0] == 0 and not subjschema.eid in pendings: |
|
1014 ptypes = session.transaction_data.setdefault('pendingrtypes', set()) |
|
1015 ptypes.add(rschema.type) |
|
1016 DropColumn(session, table=SQL_PREFIX + subjschema.type, |
|
1017 column=SQL_PREFIX + rschema.type) |
|
1018 elif lastrel: |
|
1019 DropRelationTable(session, rschema.type) |
|
1020 # if this is the last instance, drop associated relation type |
|
1021 if lastrel and not self.eidto in pendings: |
|
1022 execute('DELETE CWRType X WHERE X eid %(x)s', {'x': self.eidto}) |
|
1023 MemSchemaRDefDel(session, (subjschema, rschema, objschema)) |
|
1024 |
1023 |
1025 |
1024 |
1026 # CWAttribute / CWRelation hooks ############################################### |
1025 # CWAttribute / CWRelation hooks ############################################### |
1027 |
1026 |
1028 class AfterAddCWAttributeHook(SyncSchemaHook): |
1027 class AfterAddCWAttributeHook(SyncSchemaHook): |
1029 __regid__ = 'syncaddcwattribute' |
1028 __regid__ = 'syncaddcwattribute' |
1030 __select__ = SyncSchemaHook.__select__ & implements('CWAttribute') |
1029 __select__ = SyncSchemaHook.__select__ & is_instance('CWAttribute') |
1031 events = ('after_add_entity',) |
1030 events = ('after_add_entity',) |
1032 |
1031 |
1033 def __call__(self): |
1032 def __call__(self): |
1034 SourceDbCWAttributeAdd(self._cw, entity=self.entity) |
1033 CWAttributeAddOp(self._cw, entity=self.entity) |
1035 |
1034 |
1036 |
1035 |
1037 class AfterAddCWRelationHook(AfterAddCWAttributeHook): |
1036 class AfterAddCWRelationHook(AfterAddCWAttributeHook): |
1038 __regid__ = 'syncaddcwrelation' |
1037 __regid__ = 'syncaddcwrelation' |
1039 __select__ = SyncSchemaHook.__select__ & implements('CWRelation') |
1038 __select__ = SyncSchemaHook.__select__ & is_instance('CWRelation') |
1040 |
1039 |
1041 def __call__(self): |
1040 def __call__(self): |
1042 SourceDbCWRelationAdd(self._cw, entity=self.entity) |
1041 CWRelationAddOp(self._cw, entity=self.entity) |
1043 |
1042 |
1044 |
1043 |
1045 class AfterUpdateCWRDefHook(SyncSchemaHook): |
1044 class AfterUpdateCWRDefHook(SyncSchemaHook): |
1046 __regid__ = 'syncaddcwattribute' |
1045 __regid__ = 'syncaddcwattribute' |
1047 __select__ = SyncSchemaHook.__select__ & implements('CWAttribute', |
1046 __select__ = SyncSchemaHook.__select__ & is_instance('CWAttribute', |
1048 'CWRelation') |
1047 'CWRelation') |
1049 events = ('before_update_entity',) |
1048 events = ('before_update_entity',) |
1050 |
1049 |
1051 def __call__(self): |
1050 def __call__(self): |
1052 entity = self.entity |
1051 entity = self.entity |
1053 if self._cw.deleted_in_transaction(entity.eid): |
1052 if self._cw.deleted_in_transaction(entity.eid): |
1054 return |
1053 return |
1055 desttype = entity.otype.name |
1054 subjtype = entity.stype.name |
|
1055 objtype = entity.otype.name |
1056 rschema = self._cw.vreg.schema[entity.rtype.name] |
1056 rschema = self._cw.vreg.schema[entity.rtype.name] |
|
1057 # note: do not access schema rdef here, it may be added later by an |
|
1058 # operation |
1057 newvalues = {} |
1059 newvalues = {} |
1058 for prop in RelationDefinitionSchema.rproperty_defs(desttype): |
1060 for prop in RelationDefinitionSchema.rproperty_defs(objtype): |
1059 if prop == 'constraints': |
1061 if prop == 'constraints': |
1060 continue |
1062 continue |
1061 if prop == 'order': |
1063 if prop == 'order': |
1062 prop = 'ordernum' |
1064 attr = 'ordernum' |
1063 if prop in entity.edited_attributes: |
1065 else: |
1064 old, new = hook.entity_oldnewvalue(entity, prop) |
1066 attr = prop |
|
1067 if attr in entity.edited_attributes: |
|
1068 old, new = hook.entity_oldnewvalue(entity, attr) |
1065 if old != new: |
1069 if old != new: |
1066 newvalues[prop] = entity[prop] |
1070 newvalues[prop] = new |
1067 if newvalues: |
1071 if newvalues: |
1068 subjtype = entity.stype.name |
1072 RDefUpdateOp(self._cw, rschema=rschema, rdefkey=(subjtype, objtype), |
1069 MemSchemaRDefUpdate(self._cw, kobj=(subjtype, desttype), |
1073 values=newvalues) |
1070 rschema=rschema, values=newvalues) |
|
1071 SourceDbRDefUpdate(self._cw, kobj=(subjtype, desttype), |
|
1072 rschema=rschema, values=newvalues) |
|
1073 |
1074 |
1074 |
1075 |
1075 # constraints synchronization hooks ############################################ |
1076 # constraints synchronization hooks ############################################ |
1076 |
1077 |
1077 class AfterAddCWConstraintHook(SyncSchemaHook): |
1078 class AfterAddCWConstraintHook(SyncSchemaHook): |
1078 __regid__ = 'syncaddcwconstraint' |
1079 __regid__ = 'syncaddcwconstraint' |
1079 __select__ = SyncSchemaHook.__select__ & implements('CWConstraint') |
1080 __select__ = SyncSchemaHook.__select__ & is_instance('CWConstraint') |
1080 events = ('after_add_entity', 'after_update_entity') |
1081 events = ('after_add_entity', 'after_update_entity') |
1081 |
1082 |
1082 def __call__(self): |
1083 def __call__(self): |
1083 MemSchemaCWConstraintAdd(self._cw, entity=self.entity) |
1084 CWConstraintAddOp(self._cw, entity=self.entity) |
1084 SourceDbCWConstraintAdd(self._cw, entity=self.entity) |
|
1085 |
1085 |
1086 |
1086 |
1087 class AfterAddConstrainedByHook(SyncSchemaHook): |
1087 class AfterAddConstrainedByHook(SyncSchemaHook): |
|
1088 __regid__ = 'syncaddconstrainedby' |
|
1089 __select__ = SyncSchemaHook.__select__ & hook.match_rtype('constrained_by') |
|
1090 events = ('after_add_relation',) |
|
1091 |
|
1092 def __call__(self): |
|
1093 if self._cw.added_in_transaction(self.eidfrom): |
|
1094 # used by get_constraints() which is called in CWAttributeAddOp |
|
1095 self._cw.transaction_data.setdefault(self.eidfrom, []).append(self.eidto) |
|
1096 |
|
1097 |
|
1098 class BeforeDeleteConstrainedByHook(SyncSchemaHook): |
1088 __regid__ = 'syncdelconstrainedby' |
1099 __regid__ = 'syncdelconstrainedby' |
1089 __select__ = SyncSchemaHook.__select__ & hook.match_rtype('constrained_by') |
1100 __select__ = SyncSchemaHook.__select__ & hook.match_rtype('constrained_by') |
1090 events = ('after_add_relation',) |
|
1091 |
|
1092 def __call__(self): |
|
1093 if self._cw.added_in_transaction(self.eidfrom): |
|
1094 self._cw.transaction_data.setdefault(self.eidfrom, []).append(self.eidto) |
|
1095 |
|
1096 |
|
1097 class BeforeDeleteConstrainedByHook(AfterAddConstrainedByHook): |
|
1098 __regid__ = 'syncdelconstrainedby' |
|
1099 events = ('before_delete_relation',) |
1101 events = ('before_delete_relation',) |
1100 |
1102 |
1101 def __call__(self): |
1103 def __call__(self): |
1102 if self._cw.deleted_in_transaction(self.eidfrom): |
1104 if self._cw.deleted_in_transaction(self.eidfrom): |
1103 return |
1105 return |