26 from logilab.common.decorators import cached, clear_cache |
26 from logilab.common.decorators import cached, clear_cache |
27 from logilab.common.configuration import Method |
27 from logilab.common.configuration import Method |
28 from logilab.common.shellutils import getlogin |
28 from logilab.common.shellutils import getlogin |
29 from logilab.database import get_db_helper |
29 from logilab.database import get_db_helper |
30 |
30 |
31 from cubicweb import UnknownEid, AuthenticationError, Binary, server, neg_role |
31 from cubicweb import UnknownEid, AuthenticationError, ValidationError, Binary |
32 from cubicweb import transaction as tx |
32 from cubicweb import transaction as tx, server, neg_role |
33 from cubicweb.schema import VIRTUAL_RTYPES |
33 from cubicweb.schema import VIRTUAL_RTYPES |
34 from cubicweb.cwconfig import CubicWebNoAppConfiguration |
34 from cubicweb.cwconfig import CubicWebNoAppConfiguration |
35 from cubicweb.server import hook |
35 from cubicweb.server import hook |
36 from cubicweb.server.utils import crypt_password |
36 from cubicweb.server.utils import crypt_password, eschema_eid |
37 from cubicweb.server.sqlutils import SQL_PREFIX, SQLAdapterMixIn |
37 from cubicweb.server.sqlutils import SQL_PREFIX, SQLAdapterMixIn |
38 from cubicweb.server.rqlannotation import set_qdata |
38 from cubicweb.server.rqlannotation import set_qdata |
|
39 from cubicweb.server.hook import CleanupDeletedEidsCacheOp |
39 from cubicweb.server.session import hooks_control, security_enabled |
40 from cubicweb.server.session import hooks_control, security_enabled |
40 from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results |
41 from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results |
41 from cubicweb.server.sources.rql2sql import SQLGenerator |
42 from cubicweb.server.sources.rql2sql import SQLGenerator |
42 |
43 |
43 |
44 |
125 "Can't restore %(role)s relation %(rtype)s to entity %(eid)s which " |
126 "Can't restore %(role)s relation %(rtype)s to entity %(eid)s which " |
126 "is already linked using this relation.") |
127 "is already linked using this relation.") |
127 % {'role': neg_role(role), |
128 % {'role': neg_role(role), |
128 'rtype': rdef.rtype, |
129 'rtype': rdef.rtype, |
129 'eid': tentity.eid}) |
130 'eid': tentity.eid}) |
|
131 |
|
132 def _undo_rel_info(session, subj, rtype, obj): |
|
133 entities = [] |
|
134 for role, eid in (('subject', subj), ('object', obj)): |
|
135 try: |
|
136 entities.append(session.entity_from_eid(eid)) |
|
137 except UnknownEid: |
|
138 raise UndoException(session._( |
|
139 "Can't restore relation %(rtype)s, %(role)s entity %(eid)s" |
|
140 " doesn't exist anymore.") |
|
141 % {'role': session._(role), |
|
142 'rtype': session._(rtype), |
|
143 'eid': eid}) |
|
144 sentity, oentity = entities |
|
145 try: |
|
146 rschema = session.vreg.schema.rschema(rtype) |
|
147 rdef = rschema.rdefs[(sentity.__regid__, oentity.__regid__)] |
|
148 except KeyError: |
|
149 raise UndoException(session._( |
|
150 "Can't restore relation %(rtype)s between %(subj)s and " |
|
151 "%(obj)s, that relation does not exists anymore in the " |
|
152 "schema.") |
|
153 % {'rtype': session._(rtype), |
|
154 'subj': subj, |
|
155 'obj': obj}) |
|
156 return sentity, oentity, rdef |
|
157 |
|
158 def _undo_has_later_transaction(session, eid): |
|
159 return session.system_sql('''\ |
|
160 SELECT T.tx_uuid FROM transactions AS TREF, transactions AS T |
|
161 WHERE TREF.tx_uuid='%(txuuid)s' AND T.tx_uuid!='%(txuuid)s' |
|
162 AND T.tx_time>=TREF.tx_time |
|
163 AND (EXISTS(SELECT 1 FROM tx_entity_actions AS TEA |
|
164 WHERE TEA.tx_uuid=T.tx_uuid AND TEA.eid=%(eid)s) |
|
165 OR EXISTS(SELECT 1 FROM tx_relation_actions as TRA |
|
166 WHERE TRA.tx_uuid=T.tx_uuid AND ( |
|
167 TRA.eid_from=%(eid)s OR TRA.eid_to=%(eid)s)) |
|
168 )''' % {'txuuid': session.transaction_data['undoing_uuid'], |
|
169 'eid': eid}).fetchone() |
130 |
170 |
131 |
171 |
132 class NativeSQLSource(SQLAdapterMixIn, AbstractSource): |
172 class NativeSQLSource(SQLAdapterMixIn, AbstractSource): |
133 """adapter for source using the native cubicweb schema (see below) |
173 """adapter for source using the native cubicweb schema (see below) |
134 """ |
174 """ |
505 self.doexec(session, sql, attrs) |
545 self.doexec(session, sql, attrs) |
506 |
546 |
507 def delete_relation(self, session, subject, rtype, object): |
547 def delete_relation(self, session, subject, rtype, object): |
508 """delete a relation from the source""" |
548 """delete a relation from the source""" |
509 rschema = self.schema.rschema(rtype) |
549 rschema = self.schema.rschema(rtype) |
510 if rschema.inlined: |
550 self._delete_relation(session, subject, rtype, object, rschema.inlined) |
|
551 if session.undoable_action('R', rtype): |
|
552 self._record_tx_action(session, 'tx_relation_actions', 'R', |
|
553 eid_from=subject, rtype=rtype, eid_to=object) |
|
554 |
|
555 def _delete_relation(self, session, subject, rtype, object, inlined=False): |
|
556 """delete a relation from the source""" |
|
557 if inlined: |
511 table = SQL_PREFIX + session.describe(subject)[0] |
558 table = SQL_PREFIX + session.describe(subject)[0] |
512 column = SQL_PREFIX + rtype |
559 column = SQL_PREFIX + rtype |
513 sql = 'UPDATE %s SET %s=NULL WHERE %seid=%%(eid)s' % (table, column, |
560 sql = 'UPDATE %s SET %s=NULL WHERE %seid=%%(eid)s' % (table, column, |
514 SQL_PREFIX) |
561 SQL_PREFIX) |
515 attrs = {'eid' : subject} |
562 attrs = {'eid' : subject} |
516 else: |
563 else: |
517 attrs = {'eid_from': subject, 'eid_to': object} |
564 attrs = {'eid_from': subject, 'eid_to': object} |
518 sql = self.sqlgen.delete('%s_relation' % rtype, attrs) |
565 sql = self.sqlgen.delete('%s_relation' % rtype, attrs) |
519 self.doexec(session, sql, attrs) |
566 self.doexec(session, sql, attrs) |
520 if session.undoable_action('R', rtype): |
|
521 self._record_tx_action(session, 'tx_relation_actions', 'R', |
|
522 eid_from=subject, rtype=rtype, eid_to=object) |
|
523 |
567 |
524 def doexec(self, session, query, args=None, rollback=True): |
568 def doexec(self, session, query, args=None, rollback=True): |
525 """Execute a query. |
569 """Execute a query. |
526 it's a function just so that it shows up in profiling |
570 it's a function just so that it shows up in profiling |
527 """ |
571 """ |
945 return errors |
989 return errors |
946 |
990 |
947 def _undo_r(self, session, action): |
991 def _undo_r(self, session, action): |
948 """undo a relation removal""" |
992 """undo a relation removal""" |
949 errors = [] |
993 errors = [] |
950 err = errors.append |
|
951 _ = session._ |
|
952 subj, rtype, obj = action.eid_from, action.rtype, action.eid_to |
994 subj, rtype, obj = action.eid_from, action.rtype, action.eid_to |
953 entities = [] |
995 try: |
954 for role, eid in (('subject', subj), ('object', obj)): |
996 sentity, oentity, rdef = _undo_rel_info(session, subj, rtype, obj) |
955 try: |
997 except UndoException, ex: |
956 entities.append(session.entity_from_eid(eid)) |
998 errors.append(unicode(ex)) |
957 except UnknownEid: |
|
958 err(_("Can't restore relation %(rtype)s, %(role)s entity %(eid)s" |
|
959 " doesn't exist anymore.") |
|
960 % {'role': _(role), |
|
961 'rtype': _(rtype), |
|
962 'eid': eid}) |
|
963 if not len(entities) == 2: |
|
964 return errors |
|
965 sentity, oentity = entities |
|
966 try: |
|
967 rschema = self.schema.rschema(rtype) |
|
968 rdef = rschema.rdefs[(sentity.__regid__, oentity.__regid__)] |
|
969 except KeyError: |
|
970 err(_("Can't restore relation %(rtype)s between %(subj)s and " |
|
971 "%(obj)s, that relation does not exists anymore in the " |
|
972 "schema.") |
|
973 % {'rtype': rtype, |
|
974 'subj': subj, |
|
975 'obj': obj}) |
|
976 else: |
999 else: |
977 for role, entity in (('subject', sentity), |
1000 for role, entity in (('subject', sentity), |
978 ('object', oentity)): |
1001 ('object', oentity)): |
979 try: |
1002 try: |
980 _undo_check_relation_target(entity, rdef, role) |
1003 _undo_check_relation_target(entity, rdef, role) |
981 except UndoException, ex: |
1004 except UndoException, ex: |
982 err(unicode(ex)) |
1005 errors.append(unicode(ex)) |
983 continue |
1006 continue |
984 if not errors: |
1007 if not errors: |
985 self.repo.hm.call_hooks('before_add_relation', session, |
1008 self.repo.hm.call_hooks('before_add_relation', session, |
986 eidfrom=subj, rtype=rtype, eidto=obj) |
1009 eidfrom=subj, rtype=rtype, eidto=obj) |
987 # add relation in the database |
1010 # add relation in the database |
988 self._add_relation(session, subj, rtype, obj, rschema.inlined) |
1011 self._add_relation(session, subj, rtype, obj, rdef.rtype.inlined) |
989 # set related cache |
1012 # set related cache |
990 session.update_rel_cache_add(subj, rtype, obj, rschema.symmetric) |
1013 session.update_rel_cache_add(subj, rtype, obj, rdef.rtype.symmetric) |
991 self.repo.hm.call_hooks('after_add_relation', session, |
1014 self.repo.hm.call_hooks('after_add_relation', session, |
992 eidfrom=subj, rtype=rtype, eidto=obj) |
1015 eidfrom=subj, rtype=rtype, eidto=obj) |
993 return errors |
1016 return errors |
994 |
1017 |
995 def _undo_c(self, session, action): |
1018 def _undo_c(self, session, action): |
996 """undo an entity creation""" |
1019 """undo an entity creation""" |
997 return ['undoing of entity creation not yet supported.'] |
1020 eid = action.eid |
|
1021 # XXX done to avoid fetching all remaining relation for the entity |
|
1022 # we should find an efficient way to do this (keeping current veolidf |
|
1023 # massive deletion performance) |
|
1024 if _undo_has_later_transaction(session, eid): |
|
1025 msg = session._('some later transaction(s) touch entity, undo them ' |
|
1026 'first') |
|
1027 raise ValidationError(eid, {None: msg}) |
|
1028 etype = action.etype |
|
1029 # get an entity instance |
|
1030 try: |
|
1031 entity = self.repo.vreg['etypes'].etype_class(etype)(session) |
|
1032 except Exception: |
|
1033 return [session._( |
|
1034 "Can't undo creation of entity %s of type %s, type " |
|
1035 "no more supported" % (eid, etype))] |
|
1036 entity.set_eid(eid) |
|
1037 # for proper eid/type cache update |
|
1038 hook.set_operation(session, 'pendingeids', eid, |
|
1039 CleanupDeletedEidsCacheOp) |
|
1040 self.repo.hm.call_hooks('before_delete_entity', session, entity=entity) |
|
1041 # remove is / is_instance_of which are added using sql by hooks, hence |
|
1042 # unvisible as transaction action |
|
1043 self.doexec(session, 'DELETE FROM is_relation WHERE eid_from=%s' % eid) |
|
1044 self.doexec(session, 'DELETE FROM is_instance_of_relation WHERE eid_from=%s' % eid) |
|
1045 # XXX check removal of inlined relation? |
|
1046 # delete the entity |
|
1047 attrs = {'cw_eid': eid} |
|
1048 sql = self.sqlgen.delete(SQL_PREFIX + entity.__regid__, attrs) |
|
1049 self.doexec(session, sql, attrs) |
|
1050 # remove record from entities (will update fti if needed) |
|
1051 self.delete_info(session, entity, self.uri, None) |
|
1052 self.repo.hm.call_hooks('after_delete_entity', session, entity=entity) |
|
1053 return () |
998 |
1054 |
999 def _undo_u(self, session, action): |
1055 def _undo_u(self, session, action): |
1000 """undo an entity update""" |
1056 """undo an entity update""" |
1001 return ['undoing of entity updating not yet supported.'] |
1057 return ['undoing of entity updating not yet supported.'] |
1002 |
1058 |
1003 def _undo_a(self, session, action): |
1059 def _undo_a(self, session, action): |
1004 """undo a relation addition""" |
1060 """undo a relation addition""" |
1005 return ['undoing of relation addition not yet supported.'] |
1061 errors = [] |
|
1062 subj, rtype, obj = action.eid_from, action.rtype, action.eid_to |
|
1063 try: |
|
1064 sentity, oentity, rdef = _undo_rel_info(session, subj, rtype, obj) |
|
1065 except UndoException, ex: |
|
1066 errors.append(unicode(ex)) |
|
1067 else: |
|
1068 rschema = rdef.rtype |
|
1069 if rschema.inlined: |
|
1070 sql = 'SELECT 1 FROM cw_%s WHERE cw_eid=%s and cw_%s=%s'\ |
|
1071 % (sentity.__regid__, subj, rtype, obj) |
|
1072 else: |
|
1073 sql = 'SELECT 1 FROM %s_relation WHERE eid_from=%s and eid_to=%s'\ |
|
1074 % (rtype, subj, obj) |
|
1075 cu = self.doexec(session, sql) |
|
1076 if cu.fetchone() is None: |
|
1077 errors.append(session._( |
|
1078 "Can't undo addition of relation %s from %s to %s, doesn't " |
|
1079 "exist anymore" % (rtype, subj, obj))) |
|
1080 if not errors: |
|
1081 self.repo.hm.call_hooks('before_delete_relation', session, |
|
1082 eidfrom=subj, rtype=rtype, eidto=obj) |
|
1083 # delete relation from the database |
|
1084 self._delete_relation(session, subj, rtype, obj, rschema.inlined) |
|
1085 # set related cache |
|
1086 session.update_rel_cache_del(subj, rtype, obj, rschema.symmetric) |
|
1087 self.repo.hm.call_hooks('after_delete_relation', session, |
|
1088 eidfrom=subj, rtype=rtype, eidto=obj) |
|
1089 return errors |
1006 |
1090 |
1007 # full text index handling ################################################# |
1091 # full text index handling ################################################# |
1008 |
1092 |
1009 @cached |
1093 @cached |
1010 def need_fti_indexation(self, etype): |
1094 def need_fti_indexation(self, etype): |