--- a/server/sources/native.py Tue Mar 30 11:17:50 2010 +0200
+++ b/server/sources/native.py Tue Mar 30 11:18:31 2010 +0200
@@ -28,14 +28,15 @@
from logilab.common.shellutils import getlogin
from logilab.database import get_db_helper
-from cubicweb import UnknownEid, AuthenticationError, Binary, server, neg_role
-from cubicweb import transaction as tx
+from cubicweb import UnknownEid, AuthenticationError, ValidationError, Binary
+from cubicweb import transaction as tx, server, neg_role
from cubicweb.schema import VIRTUAL_RTYPES
from cubicweb.cwconfig import CubicWebNoAppConfiguration
from cubicweb.server import hook
-from cubicweb.server.utils import crypt_password
+from cubicweb.server.utils import crypt_password, eschema_eid
from cubicweb.server.sqlutils import SQL_PREFIX, SQLAdapterMixIn
from cubicweb.server.rqlannotation import set_qdata
+from cubicweb.server.hook import CleanupDeletedEidsCacheOp
from cubicweb.server.session import hooks_control, security_enabled
from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results
from cubicweb.server.sources.rql2sql import SQLGenerator
@@ -128,6 +129,45 @@
'rtype': rdef.rtype,
'eid': tentity.eid})
+def _undo_rel_info(session, subj, rtype, obj):
+ entities = []
+ for role, eid in (('subject', subj), ('object', obj)):
+ try:
+ entities.append(session.entity_from_eid(eid))
+ except UnknownEid:
+ raise UndoException(session._(
+ "Can't restore relation %(rtype)s, %(role)s entity %(eid)s"
+ " doesn't exist anymore.")
+ % {'role': session._(role),
+ 'rtype': session._(rtype),
+ 'eid': eid})
+ sentity, oentity = entities
+ try:
+ rschema = session.vreg.schema.rschema(rtype)
+ rdef = rschema.rdefs[(sentity.__regid__, oentity.__regid__)]
+ except KeyError:
+ raise UndoException(session._(
+ "Can't restore relation %(rtype)s between %(subj)s and "
+ "%(obj)s, that relation does not exists anymore in the "
+ "schema.")
+ % {'rtype': session._(rtype),
+ 'subj': subj,
+ 'obj': obj})
+ return sentity, oentity, rdef
+
+def _undo_has_later_transaction(session, eid):
+ return session.system_sql('''\
+SELECT T.tx_uuid FROM transactions AS TREF, transactions AS T
+WHERE TREF.tx_uuid='%(txuuid)s' AND T.tx_uuid!='%(txuuid)s'
+AND T.tx_time>=TREF.tx_time
+AND (EXISTS(SELECT 1 FROM tx_entity_actions AS TEA
+ WHERE TEA.tx_uuid=T.tx_uuid AND TEA.eid=%(eid)s)
+ OR EXISTS(SELECT 1 FROM tx_relation_actions as TRA
+ WHERE TRA.tx_uuid=T.tx_uuid AND (
+ TRA.eid_from=%(eid)s OR TRA.eid_to=%(eid)s))
+ )''' % {'txuuid': session.transaction_data['undoing_uuid'],
+ 'eid': eid}).fetchone()
+
class NativeSQLSource(SQLAdapterMixIn, AbstractSource):
"""adapter for source using the native cubicweb schema (see below)
@@ -507,7 +547,14 @@
def delete_relation(self, session, subject, rtype, object):
"""delete a relation from the source"""
rschema = self.schema.rschema(rtype)
- if rschema.inlined:
+ self._delete_relation(session, subject, rtype, object, rschema.inlined)
+ if session.undoable_action('R', rtype):
+ self._record_tx_action(session, 'tx_relation_actions', 'R',
+ eid_from=subject, rtype=rtype, eid_to=object)
+
+ def _delete_relation(self, session, subject, rtype, object, inlined=False):
+ """delete a relation from the source"""
+ if inlined:
table = SQL_PREFIX + session.describe(subject)[0]
column = SQL_PREFIX + rtype
sql = 'UPDATE %s SET %s=NULL WHERE %seid=%%(eid)s' % (table, column,
@@ -517,9 +564,6 @@
attrs = {'eid_from': subject, 'eid_to': object}
sql = self.sqlgen.delete('%s_relation' % rtype, attrs)
self.doexec(session, sql, attrs)
- if session.undoable_action('R', rtype):
- self._record_tx_action(session, 'tx_relation_actions', 'R',
- eid_from=subject, rtype=rtype, eid_to=object)
def doexec(self, session, query, args=None, rollback=True):
"""Execute a query.
@@ -947,54 +991,66 @@
def _undo_r(self, session, action):
"""undo a relation removal"""
errors = []
- err = errors.append
- _ = session._
subj, rtype, obj = action.eid_from, action.rtype, action.eid_to
- entities = []
- for role, eid in (('subject', subj), ('object', obj)):
- try:
- entities.append(session.entity_from_eid(eid))
- except UnknownEid:
- err(_("Can't restore relation %(rtype)s, %(role)s entity %(eid)s"
- " doesn't exist anymore.")
- % {'role': _(role),
- 'rtype': _(rtype),
- 'eid': eid})
- if not len(entities) == 2:
- return errors
- sentity, oentity = entities
try:
- rschema = self.schema.rschema(rtype)
- rdef = rschema.rdefs[(sentity.__regid__, oentity.__regid__)]
- except KeyError:
- err(_("Can't restore relation %(rtype)s between %(subj)s and "
- "%(obj)s, that relation does not exists anymore in the "
- "schema.")
- % {'rtype': rtype,
- 'subj': subj,
- 'obj': obj})
+ sentity, oentity, rdef = _undo_rel_info(session, subj, rtype, obj)
+ except UndoException, ex:
+ errors.append(unicode(ex))
else:
for role, entity in (('subject', sentity),
('object', oentity)):
try:
_undo_check_relation_target(entity, rdef, role)
except UndoException, ex:
- err(unicode(ex))
+ errors.append(unicode(ex))
continue
if not errors:
self.repo.hm.call_hooks('before_add_relation', session,
eidfrom=subj, rtype=rtype, eidto=obj)
# add relation in the database
- self._add_relation(session, subj, rtype, obj, rschema.inlined)
+ self._add_relation(session, subj, rtype, obj, rdef.rtype.inlined)
# set related cache
- session.update_rel_cache_add(subj, rtype, obj, rschema.symmetric)
+ session.update_rel_cache_add(subj, rtype, obj, rdef.rtype.symmetric)
self.repo.hm.call_hooks('after_add_relation', session,
eidfrom=subj, rtype=rtype, eidto=obj)
return errors
def _undo_c(self, session, action):
"""undo an entity creation"""
- return ['undoing of entity creation not yet supported.']
+ eid = action.eid
+ # XXX done to avoid fetching all remaining relation for the entity
+ # we should find an efficient way to do this (keeping current veolidf
+ # massive deletion performance)
+ if _undo_has_later_transaction(session, eid):
+ msg = session._('some later transaction(s) touch entity, undo them '
+ 'first')
+ raise ValidationError(eid, {None: msg})
+ etype = action.etype
+ # get an entity instance
+ try:
+ entity = self.repo.vreg['etypes'].etype_class(etype)(session)
+ except Exception:
+ return [session._(
+ "Can't undo creation of entity %s of type %s, type "
+ "no more supported" % (eid, etype))]
+ entity.set_eid(eid)
+ # for proper eid/type cache update
+ hook.set_operation(session, 'pendingeids', eid,
+ CleanupDeletedEidsCacheOp)
+ self.repo.hm.call_hooks('before_delete_entity', session, entity=entity)
+ # remove is / is_instance_of which are added using sql by hooks, hence
+ # unvisible as transaction action
+ self.doexec(session, 'DELETE FROM is_relation WHERE eid_from=%s' % eid)
+ self.doexec(session, 'DELETE FROM is_instance_of_relation WHERE eid_from=%s' % eid)
+ # XXX check removal of inlined relation?
+ # delete the entity
+ attrs = {'cw_eid': eid}
+ sql = self.sqlgen.delete(SQL_PREFIX + entity.__regid__, attrs)
+ self.doexec(session, sql, attrs)
+ # remove record from entities (will update fti if needed)
+ self.delete_info(session, entity, self.uri, None)
+ self.repo.hm.call_hooks('after_delete_entity', session, entity=entity)
+ return ()
def _undo_u(self, session, action):
"""undo an entity update"""
@@ -1002,7 +1058,35 @@
def _undo_a(self, session, action):
"""undo a relation addition"""
- return ['undoing of relation addition not yet supported.']
+ errors = []
+ subj, rtype, obj = action.eid_from, action.rtype, action.eid_to
+ try:
+ sentity, oentity, rdef = _undo_rel_info(session, subj, rtype, obj)
+ except UndoException, ex:
+ errors.append(unicode(ex))
+ else:
+ rschema = rdef.rtype
+ if rschema.inlined:
+ sql = 'SELECT 1 FROM cw_%s WHERE cw_eid=%s and cw_%s=%s'\
+ % (sentity.__regid__, subj, rtype, obj)
+ else:
+ sql = 'SELECT 1 FROM %s_relation WHERE eid_from=%s and eid_to=%s'\
+ % (rtype, subj, obj)
+ cu = self.doexec(session, sql)
+ if cu.fetchone() is None:
+ errors.append(session._(
+ "Can't undo addition of relation %s from %s to %s, doesn't "
+ "exist anymore" % (rtype, subj, obj)))
+ if not errors:
+ self.repo.hm.call_hooks('before_delete_relation', session,
+ eidfrom=subj, rtype=rtype, eidto=obj)
+ # delete relation from the database
+ self._delete_relation(session, subj, rtype, obj, rschema.inlined)
+ # set related cache
+ session.update_rel_cache_del(subj, rtype, obj, rschema.symmetric)
+ self.repo.hm.call_hooks('after_delete_relation', session,
+ eidfrom=subj, rtype=rtype, eidto=obj)
+ return errors
# full text index handling #################################################