368 continue |
403 continue |
369 |
404 |
370 def add_entity(self, session, entity): |
405 def add_entity(self, session, entity): |
371 """add a new entity to the source""" |
406 """add a new entity to the source""" |
372 attrs = self.preprocess_entity(entity) |
407 attrs = self.preprocess_entity(entity) |
373 sql = self.sqlgen.insert(SQL_PREFIX + str(entity.e_schema), attrs) |
408 sql = self.sqlgen.insert(SQL_PREFIX + entity.__regid__, attrs) |
374 self.doexec(session, sql, attrs) |
409 self.doexec(session, sql, attrs) |
|
410 if session.undoable_action('C', entity.__regid__): |
|
411 self._record_tx_action(session, 'tx_entity_actions', 'C', |
|
412 etype=entity.__regid__, eid=entity.eid) |
375 |
413 |
376 def update_entity(self, session, entity): |
414 def update_entity(self, session, entity): |
377 """replace an entity in the source""" |
415 """replace an entity in the source""" |
378 attrs = self.preprocess_entity(entity) |
416 attrs = self.preprocess_entity(entity) |
379 sql = self.sqlgen.update(SQL_PREFIX + str(entity.e_schema), attrs, |
417 if session.undoable_action('U', entity.__regid__): |
380 [SQL_PREFIX + 'eid']) |
418 changes = self._save_attrs(session, entity, attrs) |
|
419 self._record_tx_action(session, 'tx_entity_actions', 'U', |
|
420 etype=entity.__regid__, eid=entity.eid, |
|
421 changes=self._binary(dumps(changes))) |
|
422 sql = self.sqlgen.update(SQL_PREFIX + entity.__regid__, attrs, |
|
423 ['cw_eid']) |
381 self.doexec(session, sql, attrs) |
424 self.doexec(session, sql, attrs) |
382 |
425 |
383 def delete_entity(self, session, etype, eid): |
426 def delete_entity(self, session, entity): |
384 """delete an entity from the source""" |
427 """delete an entity from the source""" |
385 attrs = {SQL_PREFIX + 'eid': eid} |
428 if session.undoable_action('D', entity.__regid__): |
386 sql = self.sqlgen.delete(SQL_PREFIX + etype, attrs) |
429 attrs = [SQL_PREFIX + r.type |
|
430 for r in entity.e_schema.subject_relations() |
|
431 if (r.final or r.inlined) and not r in VIRTUAL_RTYPES] |
|
432 changes = self._save_attrs(session, entity, attrs) |
|
433 self._record_tx_action(session, 'tx_entity_actions', 'D', |
|
434 etype=entity.__regid__, eid=entity.eid, |
|
435 changes=self._binary(dumps(changes))) |
|
436 attrs = {'cw_eid': entity.eid} |
|
437 sql = self.sqlgen.delete(SQL_PREFIX + entity.__regid__, attrs) |
387 self.doexec(session, sql, attrs) |
438 self.doexec(session, sql, attrs) |
388 |
439 |
389 def add_relation(self, session, subject, rtype, object, inlined=False): |
440 def _add_relation(self, session, subject, rtype, object, inlined=False): |
390 """add a relation to the source""" |
441 """add a relation to the source""" |
391 if inlined is False: |
442 if inlined is False: |
392 attrs = {'eid_from': subject, 'eid_to': object} |
443 attrs = {'eid_from': subject, 'eid_to': object} |
393 sql = self.sqlgen.insert('%s_relation' % rtype, attrs) |
444 sql = self.sqlgen.insert('%s_relation' % rtype, attrs) |
394 else: # used by data import |
445 else: # used by data import |
395 etype = session.describe(subject)[0] |
446 etype = session.describe(subject)[0] |
396 attrs = {SQL_PREFIX + 'eid': subject, SQL_PREFIX + rtype: object} |
447 attrs = {'cw_eid': subject, SQL_PREFIX + rtype: object} |
397 sql = self.sqlgen.update(SQL_PREFIX + etype, attrs, |
448 sql = self.sqlgen.update(SQL_PREFIX + etype, attrs, |
398 [SQL_PREFIX + 'eid']) |
449 ['cw_eid']) |
399 self.doexec(session, sql, attrs) |
450 self.doexec(session, sql, attrs) |
|
451 |
|
452 def add_relation(self, session, subject, rtype, object, inlined=False): |
|
453 """add a relation to the source""" |
|
454 self._add_relation(session, subject, rtype, object, inlined) |
|
455 if session.undoable_action('A', rtype): |
|
456 self._record_tx_action(session, 'tx_relation_actions', 'A', |
|
457 eid_from=subject, rtype=rtype, eid_to=object) |
400 |
458 |
401 def delete_relation(self, session, subject, rtype, object): |
459 def delete_relation(self, session, subject, rtype, object): |
402 """delete a relation from the source""" |
460 """delete a relation from the source""" |
403 rschema = self.schema.rschema(rtype) |
461 rschema = self.schema.rschema(rtype) |
404 if rschema.inlined: |
462 if rschema.inlined: |
527 cursor = self.doexec(session, sql) |
592 cursor = self.doexec(session, sql) |
528 return cursor.fetchone()[0] |
593 return cursor.fetchone()[0] |
529 finally: |
594 finally: |
530 self._eid_creation_lock.release() |
595 self._eid_creation_lock.release() |
531 |
596 |
532 def add_info(self, session, entity, source, extid=None, complete=True): |
597 def add_info(self, session, entity, source, extid, complete): |
533 """add type and source info for an eid into the system table""" |
598 """add type and source info for an eid into the system table""" |
534 # begin by inserting eid/type/source/extid into the entities table |
599 # begin by inserting eid/type/source/extid into the entities table |
535 if extid is not None: |
600 if extid is not None: |
536 assert isinstance(extid, str) |
601 assert isinstance(extid, str) |
537 extid = b64encode(extid) |
602 extid = b64encode(extid) |
538 attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid, |
603 attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid, |
539 'source': source.uri, 'mtime': datetime.now()} |
604 'source': source.uri, 'mtime': datetime.now()} |
540 session.system_sql(self.sqlgen.insert('entities', attrs), attrs) |
605 self.doexec(session, self.sqlgen.insert('entities', attrs), attrs) |
541 # now we can update the full text index |
606 # now we can update the full text index |
542 if self.do_fti and self.need_fti_indexation(entity.__regid__): |
607 if self.do_fti and self.need_fti_indexation(entity.__regid__): |
543 if complete: |
608 if complete: |
544 entity.complete(entity.e_schema.indexable_attributes()) |
609 entity.complete(entity.e_schema.indexable_attributes()) |
545 FTIndexEntityOp(session, entity=entity) |
610 FTIndexEntityOp(session, entity=entity) |
546 |
611 |
547 def update_info(self, session, entity, need_fti_update): |
612 def update_info(self, session, entity, need_fti_update): |
|
613 """mark entity as being modified, fulltext reindex if needed""" |
548 if self.do_fti and need_fti_update: |
614 if self.do_fti and need_fti_update: |
549 # reindex the entity only if this query is updating at least |
615 # reindex the entity only if this query is updating at least |
550 # one indexable attribute |
616 # one indexable attribute |
551 FTIndexEntityOp(session, entity=entity) |
617 FTIndexEntityOp(session, entity=entity) |
552 # update entities.mtime |
618 # update entities.mtime |
553 attrs = {'eid': entity.eid, 'mtime': datetime.now()} |
619 attrs = {'eid': entity.eid, 'mtime': datetime.now()} |
554 session.system_sql(self.sqlgen.update('entities', attrs, ['eid']), attrs) |
620 self.doexec(session, self.sqlgen.update('entities', attrs, ['eid']), attrs) |
555 |
621 |
556 def delete_info(self, session, eid, etype, uri, extid): |
622 def delete_info(self, session, entity, uri, extid): |
557 """delete system information on deletion of an entity by transfering |
623 """delete system information on deletion of an entity by transfering |
558 record from the entities table to the deleted_entities table |
624 record from the entities table to the deleted_entities table |
559 """ |
625 """ |
560 attrs = {'eid': eid} |
626 attrs = {'eid': entity.eid} |
561 session.system_sql(self.sqlgen.delete('entities', attrs), attrs) |
627 self.doexec(session, self.sqlgen.delete('entities', attrs), attrs) |
562 if extid is not None: |
628 if extid is not None: |
563 assert isinstance(extid, str), type(extid) |
629 assert isinstance(extid, str), type(extid) |
564 extid = b64encode(extid) |
630 extid = b64encode(extid) |
565 attrs = {'type': etype, 'eid': eid, 'extid': extid, |
631 attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid, |
566 'source': uri, 'dtime': datetime.now()} |
632 'source': uri, 'dtime': datetime.now(), |
567 session.system_sql(self.sqlgen.insert('deleted_entities', attrs), attrs) |
633 } |
|
634 self.doexec(session, self.sqlgen.insert('deleted_entities', attrs), attrs) |
568 |
635 |
569 def modified_entities(self, session, etypes, mtime): |
636 def modified_entities(self, session, etypes, mtime): |
570 """return a 2-uple: |
637 """return a 2-uple: |
571 * list of (etype, eid) of entities of the given types which have been |
638 * list of (etype, eid) of entities of the given types which have been |
572 modified since the given timestamp (actually entities whose full text |
639 modified since the given timestamp (actually entities whose full text |
573 index content has changed) |
640 index content has changed) |
574 * list of (etype, eid) of entities of the given types which have been |
641 * list of (etype, eid) of entities of the given types which have been |
575 deleted since the given timestamp |
642 deleted since the given timestamp |
576 """ |
643 """ |
577 modsql = _modified_sql('entities', etypes) |
644 modsql = _modified_sql('entities', etypes) |
578 cursor = session.system_sql(modsql, {'time': mtime}) |
645 cursor = self.doexec(session, modsql, {'time': mtime}) |
579 modentities = cursor.fetchall() |
646 modentities = cursor.fetchall() |
580 delsql = _modified_sql('deleted_entities', etypes) |
647 delsql = _modified_sql('deleted_entities', etypes) |
581 cursor = session.system_sql(delsql, {'time': mtime}) |
648 cursor = self.doexec(session, delsql, {'time': mtime}) |
582 delentities = cursor.fetchall() |
649 delentities = cursor.fetchall() |
583 return modentities, delentities |
650 return modentities, delentities |
|
651 |
|
652 # undo support ############################################################# |
|
653 |
|
654 def undoable_transactions(self, session, ueid=None, **actionfilters): |
|
655 """See :class:`cubicweb.dbapi.Connection.undoable_transactions`""" |
|
656 # force filtering to session's user if not a manager |
|
657 if not session.user.is_in_group('managers'): |
|
658 ueid = session.user.eid |
|
659 restr = {} |
|
660 if ueid is not None: |
|
661 restr['tx_user'] = ueid |
|
662 sql = self.sqlgen.select('transactions', restr, ('tx_uuid', 'tx_time', 'tx_user')) |
|
663 if actionfilters: |
|
664 # we will need subqueries to filter transactions according to |
|
665 # actions done |
|
666 tearestr = {} # filters on the tx_entity_actions table |
|
667 trarestr = {} # filters on the tx_relation_actions table |
|
668 genrestr = {} # generic filters, appliyable to both table |
|
669 # unless public explicitly set to false, we only consider public |
|
670 # actions |
|
671 if actionfilters.pop('public', True): |
|
672 genrestr['txa_public'] = True |
|
673 # put additional filters in trarestr and/or tearestr |
|
674 for key, val in actionfilters.iteritems(): |
|
675 if key == 'etype': |
|
676 # filtering on etype implies filtering on entity actions |
|
677 # only, and with no eid specified |
|
678 assert actionfilters.get('action', 'C') in 'CUD' |
|
679 assert not 'eid' in actionfilters |
|
680 tearestr['etype'] = val |
|
681 elif key == 'eid': |
|
682 # eid filter may apply to 'eid' of tx_entity_actions or to |
|
683 # 'eid_from' OR 'eid_to' of tx_relation_actions |
|
684 if actionfilters.get('action', 'C') in 'CUD': |
|
685 tearestr['eid'] = val |
|
686 if actionfilters.get('action', 'A') in 'AR': |
|
687 trarestr['eid_from'] = val |
|
688 trarestr['eid_to'] = val |
|
689 elif key == 'action': |
|
690 if val in 'CUD': |
|
691 tearestr['txa_action'] = val |
|
692 else: |
|
693 assert val in 'AR' |
|
694 trarestr['txa_action'] = val |
|
695 else: |
|
696 raise AssertionError('unknow filter %s' % key) |
|
697 assert trarestr or tearestr, "can't only filter on 'public'" |
|
698 subqsqls = [] |
|
699 # append subqueries to the original query, using EXISTS() |
|
700 if trarestr or (genrestr and not tearestr): |
|
701 trarestr.update(genrestr) |
|
702 trasql = self.sqlgen.select('tx_relation_actions', trarestr, ('1',)) |
|
703 if 'eid_from' in trarestr: |
|
704 # replace AND by OR between eid_from/eid_to restriction |
|
705 trasql = sql_or_clauses(trasql, ['eid_from = %(eid_from)s', |
|
706 'eid_to = %(eid_to)s']) |
|
707 trasql += ' AND transactions.tx_uuid=tx_relation_actions.tx_uuid' |
|
708 subqsqls.append('EXISTS(%s)' % trasql) |
|
709 if tearestr or (genrestr and not trarestr): |
|
710 tearestr.update(genrestr) |
|
711 teasql = self.sqlgen.select('tx_entity_actions', tearestr, ('1',)) |
|
712 teasql += ' AND transactions.tx_uuid=tx_entity_actions.tx_uuid' |
|
713 subqsqls.append('EXISTS(%s)' % teasql) |
|
714 if restr: |
|
715 sql += ' AND %s' % ' OR '.join(subqsqls) |
|
716 else: |
|
717 sql += ' WHERE %s' % ' OR '.join(subqsqls) |
|
718 restr.update(trarestr) |
|
719 restr.update(tearestr) |
|
720 # we want results ordered by transaction's time descendant |
|
721 sql += ' ORDER BY tx_time DESC' |
|
722 cu = self.doexec(session, sql, restr) |
|
723 # turn results into transaction objects |
|
724 return [tx.Transaction(*args) for args in cu.fetchall()] |
|
725 |
|
726 def tx_info(self, session, txuuid): |
|
727 """See :class:`cubicweb.dbapi.Connection.transaction_info`""" |
|
728 return tx.Transaction(txuuid, *self._tx_info(session, txuuid)) |
|
729 |
|
730 def tx_actions(self, session, txuuid, public): |
|
731 """See :class:`cubicweb.dbapi.Connection.transaction_actions`""" |
|
732 self._tx_info(session, txuuid) |
|
733 restr = {'tx_uuid': txuuid} |
|
734 if public: |
|
735 restr['txa_public'] = True |
|
736 sql = self.sqlgen.select('tx_entity_actions', restr, |
|
737 ('txa_action', 'txa_public', 'txa_order', |
|
738 'etype', 'eid', 'changes')) |
|
739 cu = self.doexec(session, sql, restr) |
|
740 actions = [tx.EntityAction(a,p,o,et,e,c and loads(self.binary_to_str(c))) |
|
741 for a,p,o,et,e,c in cu.fetchall()] |
|
742 sql = self.sqlgen.select('tx_relation_actions', restr, |
|
743 ('txa_action', 'txa_public', 'txa_order', |
|
744 'rtype', 'eid_from', 'eid_to')) |
|
745 cu = self.doexec(session, sql, restr) |
|
746 actions += [tx.RelationAction(*args) for args in cu.fetchall()] |
|
747 return sorted(actions, key=lambda x: x.order) |
|
748 |
|
749 def undo_transaction(self, session, txuuid): |
|
750 """See :class:`cubicweb.dbapi.Connection.undo_transaction`""" |
|
751 # set mode so pool isn't released subsquently until commit/rollback |
|
752 session.mode = 'write' |
|
753 errors = [] |
|
754 with hooks_control(session, session.HOOKS_DENY_ALL, 'integrity'): |
|
755 for action in reversed(self.tx_actions(session, txuuid, False)): |
|
756 undomethod = getattr(self, '_undo_%s' % action.action.lower()) |
|
757 errors += undomethod(session, action) |
|
758 # remove the transactions record |
|
759 self.doexec(session, |
|
760 "DELETE FROM transactions WHERE tx_uuid='%s'" % txuuid) |
|
761 return errors |
|
762 |
|
763 def start_undoable_transaction(self, session, uuid): |
|
764 """session callback to insert a transaction record in the transactions |
|
765 table when some undoable transaction is started |
|
766 """ |
|
767 ueid = session.user.eid |
|
768 attrs = {'tx_uuid': uuid, 'tx_user': ueid, 'tx_time': datetime.now()} |
|
769 self.doexec(session, self.sqlgen.insert('transactions', attrs), attrs) |
|
770 |
|
771 def _save_attrs(self, session, entity, attrs): |
|
772 """return a pickleable dictionary containing current values for given |
|
773 attributes of the entity |
|
774 """ |
|
775 restr = {'cw_eid': entity.eid} |
|
776 sql = self.sqlgen.select(SQL_PREFIX + entity.__regid__, restr, attrs) |
|
777 cu = self.doexec(session, sql, restr) |
|
778 values = dict(zip(attrs, cu.fetchone())) |
|
779 # ensure backend specific binary are converted back to string |
|
780 eschema = entity.e_schema |
|
781 for column in attrs: |
|
782 # [3:] remove 'cw_' prefix |
|
783 attr = column[3:] |
|
784 if not eschema.subjrels[attr].final: |
|
785 continue |
|
786 if eschema.destination(attr) in ('Password', 'Bytes'): |
|
787 value = values[column] |
|
788 if value is not None: |
|
789 values[column] = self.binary_to_str(value) |
|
790 return values |
|
791 |
|
792 def _record_tx_action(self, session, table, action, **kwargs): |
|
793 """record a transaction action in the given table (either |
|
794 'tx_entity_actions' or 'tx_relation_action') |
|
795 """ |
|
796 kwargs['tx_uuid'] = session.transaction_uuid() |
|
797 kwargs['txa_action'] = action |
|
798 kwargs['txa_order'] = session.transaction_inc_action_counter() |
|
799 kwargs['txa_public'] = session.running_dbapi_query |
|
800 self.doexec(session, self.sqlgen.insert(table, kwargs), kwargs) |
|
801 |
|
802 def _tx_info(self, session, txuuid): |
|
803 """return transaction's time and user of the transaction with the given uuid. |
|
804 |
|
805 raise `NoSuchTransaction` if there is no such transaction of if the |
|
806 session's user isn't allowed to see it. |
|
807 """ |
|
808 restr = {'tx_uuid': txuuid} |
|
809 sql = self.sqlgen.select('transactions', restr, ('tx_time', 'tx_user')) |
|
810 cu = self.doexec(session, sql, restr) |
|
811 try: |
|
812 time, ueid = cu.fetchone() |
|
813 except TypeError: |
|
814 raise tx.NoSuchTransaction() |
|
815 if not (session.user.is_in_group('managers') |
|
816 or session.user.eid == ueid): |
|
817 raise tx.NoSuchTransaction() |
|
818 return time, ueid |
|
819 |
|
820 def _undo_d(self, session, action): |
|
821 """undo an entity deletion""" |
|
822 errors = [] |
|
823 err = errors.append |
|
824 eid = action.eid |
|
825 etype = action.etype |
|
826 _ = session._ |
|
827 # get an entity instance |
|
828 try: |
|
829 entity = self.repo.vreg['etypes'].etype_class(etype)(session) |
|
830 except Exception: |
|
831 err("can't restore entity %s of type %s, type no more supported" |
|
832 % (eid, etype)) |
|
833 return errors |
|
834 # check for schema changes, entities linked through inlined relation |
|
835 # still exists, rewrap binary values |
|
836 eschema = entity.e_schema |
|
837 getrschema = eschema.subjrels |
|
838 for column, value in action.changes.items(): |
|
839 rtype = column[3:] # remove cw_ prefix |
|
840 try: |
|
841 rschema = getrschema[rtype] |
|
842 except KeyError: |
|
843 err(_("Can't restore relation %(rtype)s of entity %(eid)s, " |
|
844 "this relation does not exists anymore in the schema.") |
|
845 % {'rtype': rtype, 'eid': eid}) |
|
846 if not rschema.final: |
|
847 assert value is None |
|
848 # try: |
|
849 # tentity = session.entity_from_eid(eid) |
|
850 # except UnknownEid: |
|
851 # err(_("Can't restore %(role)s relation %(rtype)s to " |
|
852 # "entity %(eid)s which doesn't exist anymore.") |
|
853 # % {'role': _('subject'), |
|
854 # 'rtype': _(rtype), |
|
855 # 'eid': eid}) |
|
856 # continue |
|
857 # rdef = rdefs[(eschema, tentity.__regid__)] |
|
858 # try: |
|
859 # _undo_check_relation_target(tentity, rdef, 'object') |
|
860 # except UndoException, ex: |
|
861 # err(unicode(ex)) |
|
862 # continue |
|
863 # if rschema.inlined: |
|
864 # entity[rtype] = value |
|
865 # else: |
|
866 # # restore relation where inlined changed since the deletion |
|
867 # del action.changes[column] |
|
868 # self._add_relation(session, subject, rtype, object) |
|
869 # # set related cache |
|
870 # session.update_rel_cache_add(eid, rtype, value, |
|
871 # rschema.symmetric) |
|
872 elif eschema.destination(rtype) in ('Bytes', 'Password'): |
|
873 action.changes[column] = self._binary(value) |
|
874 entity[rtype] = Binary(value) |
|
875 elif isinstance(value, str): |
|
876 entity[rtype] = unicode(value, session.encoding, 'replace') |
|
877 else: |
|
878 entity[rtype] = value |
|
879 entity.set_eid(eid) |
|
880 entity.edited_attributes = set(entity) |
|
881 entity.check() |
|
882 self.repo.hm.call_hooks('before_add_entity', session, entity=entity) |
|
883 # restore the entity |
|
884 action.changes['cw_eid'] = eid |
|
885 sql = self.sqlgen.insert(SQL_PREFIX + etype, action.changes) |
|
886 self.doexec(session, sql, action.changes) |
|
887 # restore record in entities (will update fti if needed) |
|
888 self.add_info(session, entity, self, None, True) |
|
889 # remove record from deleted_entities |
|
890 self.doexec(session, 'DELETE FROM deleted_entities WHERE eid=%s' % eid) |
|
891 self.repo.hm.call_hooks('after_add_entity', session, entity=entity) |
|
892 return errors |
|
893 |
|
894 def _undo_r(self, session, action): |
|
895 """undo a relation removal""" |
|
896 errors = [] |
|
897 err = errors.append |
|
898 _ = session._ |
|
899 subj, rtype, obj = action.eid_from, action.rtype, action.eid_to |
|
900 entities = [] |
|
901 for role, eid in (('subject', subj), ('object', obj)): |
|
902 try: |
|
903 entities.append(session.entity_from_eid(eid)) |
|
904 except UnknownEid: |
|
905 err(_("Can't restore relation %(rtype)s, %(role)s entity %(eid)s" |
|
906 " doesn't exist anymore.") |
|
907 % {'role': _(role), |
|
908 'rtype': _(rtype), |
|
909 'eid': eid}) |
|
910 if not len(entities) == 2: |
|
911 return errors |
|
912 sentity, oentity = entities |
|
913 try: |
|
914 rschema = self.schema.rschema(rtype) |
|
915 rdef = rschema.rdefs[(sentity.__regid__, oentity.__regid__)] |
|
916 except KeyError: |
|
917 err(_("Can't restore relation %(rtype)s between %(subj)s and " |
|
918 "%(obj)s, that relation does not exists anymore in the " |
|
919 "schema.") |
|
920 % {'rtype': rtype, |
|
921 'subj': subj, |
|
922 'obj': obj}) |
|
923 else: |
|
924 for role, entity in (('subject', sentity), |
|
925 ('object', oentity)): |
|
926 try: |
|
927 _undo_check_relation_target(entity, rdef, role) |
|
928 except UndoException, ex: |
|
929 err(unicode(ex)) |
|
930 continue |
|
931 if not errors: |
|
932 self.repo.hm.call_hooks('before_add_relation', session, |
|
933 eidfrom=subj, rtype=rtype, eidto=obj) |
|
934 # add relation in the database |
|
935 self._add_relation(session, subj, rtype, obj, rschema.inlined) |
|
936 # set related cache |
|
937 session.update_rel_cache_add(subj, rtype, obj, rschema.symmetric) |
|
938 self.repo.hm.call_hooks('after_add_relation', session, |
|
939 eidfrom=subj, rtype=rtype, eidto=obj) |
|
940 return errors |
|
941 |
|
942 def _undo_c(self, session, action): |
|
943 """undo an entity creation""" |
|
944 return ['undoing of entity creation not yet supported.'] |
|
945 |
|
946 def _undo_u(self, session, action): |
|
947 """undo an entity update""" |
|
948 return ['undoing of entity updating not yet supported.'] |
|
949 |
|
950 def _undo_a(self, session, action): |
|
951 """undo a relation addition""" |
|
952 return ['undoing of relation addition not yet supported.'] |
584 |
953 |
585 # full text index handling ################################################# |
954 # full text index handling ################################################# |
586 |
955 |
587 @cached |
956 @cached |
588 def need_fti_indexation(self, etype): |
957 def need_fti_indexation(self, etype): |
660 eid INTEGER PRIMARY KEY NOT NULL, |
1029 eid INTEGER PRIMARY KEY NOT NULL, |
661 type VARCHAR(64) NOT NULL, |
1030 type VARCHAR(64) NOT NULL, |
662 source VARCHAR(64) NOT NULL, |
1031 source VARCHAR(64) NOT NULL, |
663 mtime %s NOT NULL, |
1032 mtime %s NOT NULL, |
664 extid VARCHAR(256) |
1033 extid VARCHAR(256) |
665 ); |
1034 );; |
666 CREATE INDEX entities_type_idx ON entities(type); |
1035 CREATE INDEX entities_type_idx ON entities(type);; |
667 CREATE INDEX entities_mtime_idx ON entities(mtime); |
1036 CREATE INDEX entities_mtime_idx ON entities(mtime);; |
668 CREATE INDEX entities_extid_idx ON entities(extid); |
1037 CREATE INDEX entities_extid_idx ON entities(extid);; |
669 |
1038 |
670 CREATE TABLE deleted_entities ( |
1039 CREATE TABLE deleted_entities ( |
671 eid INTEGER PRIMARY KEY NOT NULL, |
1040 eid INTEGER PRIMARY KEY NOT NULL, |
672 type VARCHAR(64) NOT NULL, |
1041 type VARCHAR(64) NOT NULL, |
673 source VARCHAR(64) NOT NULL, |
1042 source VARCHAR(64) NOT NULL, |
674 dtime %s NOT NULL, |
1043 dtime %s NOT NULL, |
675 extid VARCHAR(256) |
1044 extid VARCHAR(256) |
676 ); |
1045 );; |
677 CREATE INDEX deleted_entities_type_idx ON deleted_entities(type); |
1046 CREATE INDEX deleted_entities_type_idx ON deleted_entities(type);; |
678 CREATE INDEX deleted_entities_dtime_idx ON deleted_entities(dtime); |
1047 CREATE INDEX deleted_entities_dtime_idx ON deleted_entities(dtime);; |
679 CREATE INDEX deleted_entities_extid_idx ON deleted_entities(extid); |
1048 CREATE INDEX deleted_entities_extid_idx ON deleted_entities(extid);; |
680 """ % (helper.sql_create_sequence('entities_id_seq'), tstamp_col_type, tstamp_col_type) |
1049 |
|
1050 CREATE TABLE transactions ( |
|
1051 tx_uuid CHAR(32) PRIMARY KEY NOT NULL, |
|
1052 tx_user INTEGER NOT NULL, |
|
1053 tx_time %s NOT NULL |
|
1054 );; |
|
1055 CREATE INDEX transactions_tx_user_idx ON transactions(tx_user);; |
|
1056 |
|
1057 CREATE TABLE tx_entity_actions ( |
|
1058 tx_uuid CHAR(32) REFERENCES transactions(tx_uuid) ON DELETE CASCADE, |
|
1059 txa_action CHAR(1) NOT NULL, |
|
1060 txa_public %s NOT NULL, |
|
1061 txa_order INTEGER, |
|
1062 eid INTEGER NOT NULL, |
|
1063 etype VARCHAR(64) NOT NULL, |
|
1064 changes %s |
|
1065 );; |
|
1066 CREATE INDEX tx_entity_actions_txa_action_idx ON tx_entity_actions(txa_action);; |
|
1067 CREATE INDEX tx_entity_actions_txa_public_idx ON tx_entity_actions(txa_public);; |
|
1068 CREATE INDEX tx_entity_actions_eid_idx ON tx_entity_actions(eid);; |
|
1069 CREATE INDEX tx_entity_actions_etype_idx ON tx_entity_actions(etype);; |
|
1070 |
|
1071 CREATE TABLE tx_relation_actions ( |
|
1072 tx_uuid CHAR(32) REFERENCES transactions(tx_uuid) ON DELETE CASCADE, |
|
1073 txa_action CHAR(1) NOT NULL, |
|
1074 txa_public %s NOT NULL, |
|
1075 txa_order INTEGER, |
|
1076 eid_from INTEGER NOT NULL, |
|
1077 eid_to INTEGER NOT NULL, |
|
1078 rtype VARCHAR(256) NOT NULL |
|
1079 );; |
|
1080 CREATE INDEX tx_relation_actions_txa_action_idx ON tx_relation_actions(txa_action);; |
|
1081 CREATE INDEX tx_relation_actions_txa_public_idx ON tx_relation_actions(txa_public);; |
|
1082 CREATE INDEX tx_relation_actions_eid_from_idx ON tx_relation_actions(eid_from);; |
|
1083 CREATE INDEX tx_relation_actions_eid_to_idx ON tx_relation_actions(eid_to);; |
|
1084 """ % (helper.sql_create_sequence('entities_id_seq').replace(';', ';;'), |
|
1085 typemap['Datetime'], typemap['Datetime'], typemap['Datetime'], |
|
1086 typemap['Boolean'], typemap['Bytes'], typemap['Boolean']) |
|
1087 if helper.backend_name == 'sqlite': |
|
1088 # sqlite support the ON DELETE CASCADE syntax but do nothing |
|
1089 schema += ''' |
|
1090 CREATE TRIGGER fkd_transactions |
|
1091 BEFORE DELETE ON transactions |
|
1092 FOR EACH ROW BEGIN |
|
1093 DELETE FROM tx_entity_actions WHERE tx_uuid=OLD.tx_uuid; |
|
1094 DELETE FROM tx_relation_actions WHERE tx_uuid=OLD.tx_uuid; |
|
1095 END;; |
|
1096 ''' |
681 return schema |
1097 return schema |
682 |
1098 |
683 |
1099 |
684 def sql_drop_schema(driver): |
1100 def sql_drop_schema(driver): |
685 helper = get_db_helper(driver) |
1101 helper = get_db_helper(driver) |
686 return """ |
1102 return """ |
687 %s |
1103 %s |
688 DROP TABLE entities; |
1104 DROP TABLE entities; |
689 DROP TABLE deleted_entities; |
1105 DROP TABLE deleted_entities; |
|
1106 DROP TABLE transactions; |
|
1107 DROP TABLE tx_entity_actions; |
|
1108 DROP TABLE tx_relation_actions; |
690 """ % helper.sql_drop_sequence('entities_id_seq') |
1109 """ % helper.sql_drop_sequence('entities_id_seq') |
691 |
1110 |
692 |
1111 |
693 def grant_schema(user, set_owner=True): |
1112 def grant_schema(user, set_owner=True): |
694 result = '' |
1113 result = '' |
695 if set_owner: |
1114 for table in ('entities', 'deleted_entities', 'entities_id_seq', |
696 result = 'ALTER TABLE entities OWNER TO %s;\n' % user |
1115 'transactions', 'tx_entity_actions', 'tx_relation_actions'): |
697 result += 'ALTER TABLE deleted_entities OWNER TO %s;\n' % user |
1116 if set_owner: |
698 result += 'ALTER TABLE entities_id_seq OWNER TO %s;\n' % user |
1117 result = 'ALTER TABLE %s OWNER TO %s;\n' % (table, user) |
699 result += 'GRANT ALL ON entities TO %s;\n' % user |
1118 result += 'GRANT ALL ON %s TO %s;\n' % (table, user) |
700 result += 'GRANT ALL ON deleted_entities TO %s;\n' % user |
|
701 result += 'GRANT ALL ON entities_id_seq TO %s;\n' % user |
|
702 return result |
1119 return result |
703 |
1120 |
704 |
1121 |
705 class BaseAuthentifier(object): |
1122 class BaseAuthentifier(object): |
706 |
1123 |