def _rql_cache_key(cnx, rql, args, eidkeys): cachekey = [rql] type_from_eid = cnx.repo.type_from_eid for key in sorted(eidkeys): try: etype = type_from_eid(args[key], cnx) except KeyError: raise QueryError('bad cache key %s (no value)' % key) except TypeError: raise QueryError('bad cache key %s (value: %r)' % (key, args[key])) cachekey.append(etype) # ensure eid is correctly typed in args args[key] = int(args[key]) return tuple(cachekey)
def _extract_const_attributes(plan, rqlst, to_build): """add constant values to entity def, mark variables to be selected """ to_select = {} for relation in rqlst.main_relations: lhs, rhs = relation.get_variable_parts() rtype = relation.r_type if rtype in READ_ONLY_RTYPES: raise QueryError("can't assign to %s" % rtype) try: edef = to_build[str(lhs)] except KeyError: # lhs var is not to build, should be selected and added as an # object relation edef = to_build[str(rhs)] to_select.setdefault(edef, []).append((rtype, lhs, 1)) else: if isinstance(rhs, Constant) and not rhs.uid: # add constant values to entity def value = rhs.eval(plan.args) eschema = edef.entity.e_schema attrtype = eschema.subjrels[rtype].objects(eschema)[0] if attrtype == 'Password' and isinstance(value, str): value = value.encode('UTF8') edef.edited_attribute(rtype, value) elif str(rhs) in to_build: # create a relation between two newly created variables plan.add_relation_def((edef, rtype, to_build[rhs.name])) else: to_select.setdefault(edef, []).append((rtype, rhs, 0)) return to_select
def add_relation_def(self, rdef): """add an relation definition to build""" edef, rtype, value = rdef if self.schema[rtype].rule: raise QueryError("'%s' is a computed relation" % rtype) self.r_defs.add(rdef) if not isinstance(edef, int): self._r_subj_index.setdefault(edef, []).append(rdef) if not isinstance(value, int): self._r_obj_index.setdefault(value, []).append(rdef)
def build_set_plan(self, plan, rqlst): """get an execution plan from an SET RQL query""" getrschema = self.schema.rschema select = Select() # potential substep query selectedidx = {} # local state updatedefs = [] # definition of update attributes/relations selidx = residx = 0 # substep selection / resulting rset indexes # search for eid const in the WHERE clause eidconsts = _extract_eid_consts(plan, rqlst) # build `updatedefs` describing things to update and add necessary # variables to the substep selection for i, relation in enumerate(rqlst.main_relations): if relation.r_type in VIRTUAL_RTYPES: raise QueryError('can not assign to %r relation' % relation.r_type) lhs, rhs = relation.get_variable_parts() lhskey = lhs.as_string() if lhskey not in selectedidx: if lhs.variable in eidconsts: eid = eidconsts[lhs.variable] lhsinfo = (_CONSTANT, eid, residx) else: select.append_selected(lhs.copy(select)) lhsinfo = (_FROM_SUBSTEP, selidx, residx) selidx += 1 residx += 1 selectedidx[lhskey] = lhsinfo else: lhsinfo = selectedidx[lhskey][:-1] + (None, ) rhskey = rhs.as_string() if rhskey not in selectedidx: if isinstance(rhs, Constant): rhsinfo = (_CONSTANT, rhs.eval(plan.args), residx) elif getattr(rhs, 'variable', None) in eidconsts: eid = eidconsts[rhs.variable] rhsinfo = (_CONSTANT, eid, residx) else: select.append_selected(rhs.copy(select)) rhsinfo = (_FROM_SUBSTEP, selidx, residx) selidx += 1 residx += 1 selectedidx[rhskey] = rhsinfo else: rhsinfo = selectedidx[rhskey][:-1] + (None, ) rschema = getrschema(relation.r_type) updatedefs.append((lhsinfo, rhsinfo, rschema)) # the update step step = UpdateStep(plan, updatedefs) # when necessary add substep to fetch yet unknown values select = _build_substep_query(select, rqlst) if select is not None: # set distinct to avoid potential duplicate key error select.distinct = True step.children += self._select_plan(plan, select, rqlst.solutions) return (step, )
def build_delete_plan(self, plan, rqlst): """get an execution plan from a DELETE RQL query""" # build a select query to fetch entities to delete steps = [] for etype, var in rqlst.main_variables: step = DeleteEntitiesStep(plan) step.children += self._sel_variable_step(plan, rqlst, etype, var) steps.append(step) for relation in rqlst.main_relations: rtype = relation.r_type if self.schema[rtype].rule: raise QueryError("'%s' is a computed relation" % rtype) step = DeleteRelationsStep(plan, rtype) step.children += self._sel_relation_steps(plan, rqlst, relation) steps.append(step) return steps
def execute(self): """execute this step""" cnx = self.plan.cnx repo = cnx.repo edefs = {} relations = {} # insert relations if self.children: result = self.execute_child() else: result = [[]] for i, row in enumerate(result): newrow = [] for (lhsinfo, rhsinfo, rschema) in self.updatedefs: if rschema.rule: raise QueryError("'%s' is a computed relation" % rschema.type) lhsval = _handle_relterm(lhsinfo, row, newrow) rhsval = _handle_relterm(rhsinfo, row, newrow) if rschema.final or rschema.inlined: eid = int(lhsval) try: edited = edefs[eid] except KeyError: edef = cnx.entity_from_eid(eid) edefs[eid] = edited = EditedEntity(edef) edited.edited_attribute(str(rschema), rhsval) else: str_rschema = str(rschema) if str_rschema in relations: relations[str_rschema].append((lhsval, rhsval)) else: relations[str_rschema] = [(lhsval, rhsval)] result[i] = newrow # update entities repo.glob_add_relations(cnx, relations) for eid, edited in edefs.items(): repo.glob_update_entity(cnx, edited) return result
def commit(self): """commit the current session's transaction""" assert self.cnxset is not None cstate = self.commit_state if cstate == 'uncommitable': raise QueryError('transaction must be rolled back') if cstate == 'precommit': self.warn('calling commit in precommit makes no sense; ignoring commit') return if cstate == 'postcommit': self.critical('postcommit phase is not allowed to write to the db; ignoring commit') return assert cstate is None # on rollback, an operation should have the following state # information: # - processed by the precommit/commit event or not # - if processed, is it the failed operation debug = server.DEBUG & server.DBG_OPS try: # by default, operations are executed with security turned off with self.security_enabled(False, False): processed = [] self.commit_state = 'precommit' if debug: print(self.commit_state, '*' * 20) try: with self.running_hooks_ops(): while self.pending_operations: operation = self.pending_operations.pop(0) operation.processed = 'precommit' processed.append(operation) if debug: print(operation) operation.handle_event('precommit_event') self.pending_operations[:] = processed self.debug('precommit transaction %s done', self) except BaseException: # if error on [pre]commit: # # * set .failed = True on the operation causing the failure # * call revert<event>_event on processed operations # * call rollback_event on *all* operations # # that seems more natural than not calling rollback_event # for processed operations, and allow generic rollback # instead of having to implements rollback, revertprecommit # and revertcommit, that will be enough in mont case. operation.failed = True if debug: print(self.commit_state, '*' * 20) with self.running_hooks_ops(): for operation in reversed(processed): if debug: print(operation) try: operation.handle_event('revertprecommit_event') except BaseException: self.critical('error while reverting precommit', exc_info=True) # XXX use slice notation since self.pending_operations is a # read-only property. self.pending_operations[:] = processed + self.pending_operations self.rollback() raise self.cnxset.commit() self.commit_state = 'postcommit' if debug: print(self.commit_state, '*' * 20) with self.running_hooks_ops(): while self.pending_operations: operation = self.pending_operations.pop(0) if debug: print(operation) operation.processed = 'postcommit' try: operation.handle_event('postcommit_event') except BaseException: if self.repo.config.mode == 'test': raise self.critical('error while postcommit', exc_info=sys.exc_info()) self.debug('postcommit transaction %s done', self) return self.transaction_uuid(set=False) finally: self.clear()