server/sources/native.py
branchstable
changeset 5076 b0e6134b4324
parent 5075 a4b735e76c66
child 5098 32b1adfb6b92
equal deleted inserted replaced
5075:a4b735e76c66 5076:b0e6134b4324
    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):