package com.sap.finex.interpreter.expressions; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import structure.Association; import structure.Field; import structure.FinexClass; import structure.Type; import behavior.actions.Statement; import behavior.expressions.Alias; import behavior.expressions.Expression; import behavior.expressions.oql.FromClause; import behavior.expressions.oql.OqlQuery; import behavior.functions.NativeImpl; import behavior.functions.SignatureImplementation; import com.sap.finex.interpreter.FinexInterpreter; import com.sap.finex.interpreter.FinexStackFrame; import com.sap.finex.interpreter.FinexValueObject; import com.sap.finex.interpreter.objects.FinexNativeObject; import com.sap.runlet.abstractinterpreter.Interpreter; import com.sap.runlet.abstractinterpreter.objects.ClassTypedObject; import com.sap.runlet.abstractinterpreter.objects.EmptyObject; import com.sap.runlet.abstractinterpreter.objects.RunletObject; import com.sap.tc.moin.repository.mmi.reflect.JmiException; import com.sap.tc.moin.repository.shared.util.Tuple.Pair; public class OqlQueryInterpreter implements Interpreter<OqlQuery, FinexClass, Type, FinexClass, Association, Field, Statement, Expression, SignatureImplementation, FinexStackFrame, NativeImpl, FinexInterpreter> { private OqlQuery oql; public OqlQueryInterpreter(OqlQuery oql) { this.oql = oql; } /** * Traverses the OQL query's {@link OqlQuery#getFromClauses() from clauses} from left to right. * The current <tt>from</tt> clause expression is evaluated. For each resulting object, the object * is assigned to the current <tt>from</tt> clause's alias on a new stack frame which is then * used to compute the subsequent <tt>from</tt> clause in the same manner. This results in several trees * of objects for the <tt>from</tt> clauses, one tree per response for the first <tt>from</tt> clause. * * If a <tt>from</tt> clause depends on the aliases defined by the preceding <tt>from</tt> clauses, * it needs to be re-computed for each value combination of those aliases. Otherwise, the clause * can be evaluated only once, and the resulting (multi-)object can be used by all subsequent clauses * and when assembling the final tuple set.<p> * * A tree node may contain an {@link EmptyObject}, meaning that due to some outer join logic no * value was found for that <tt>from</tt> clause, but the query still demanded a record for this * case. Each level in the tree corresponds to one <tt>from</tt> clause in the query. The tree * can be navigated regardless of there being any links between the objects referenced by the nodes * (which could be no objects at all as in the {@link EmptyObject} case mentioned above).<p> * * The result set is then computed by taking the leaf node of all trees, assigning the object * therein to the last <tt>from</tt> clause's alias and traversing up the tree, keeping assigning * aliases until a root object is reached. This way, as many tuples result as there are leaf nodes * in the set of all trees. */ @Override public RunletObject<Field, Type, FinexClass> evaluate(FinexInterpreter interpreter) throws JmiException, SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { /* * "multiplication" from left to right over "from" expressions Start with first from expression and compute results; * remember; start to iterate over the result and for each compute the next from expression; if the next from expression * is independent of the previous alias, re-use the previous evaluation result of the next from expression; cache by set * of tree nodes for used aliases. * * Therefore, if a from clause does not depend on any other aliases, it should be computed only once, and the * results should be re-used. * * If the next from expression depends on one or more aliases of previous from clauses, evaluate the * next from expression and store the result together with the node set for the aliases used. This node * set is the set of cursors/iterators used in the nested loop. */ // start with an empty context and evaluate the full list of from clauses recursively Map<Pair<Expression, Set<RunletObject<Field, Type, FinexClass>>>, RunletObject<Field, Type, FinexClass>> cache = new HashMap<Pair<Expression, Set<RunletObject<Field, Type, FinexClass>>>, RunletObject<Field, Type, FinexClass>>(); // use a single empty row as the "neutral element" for from clause "multiplication" to get things started List<Map<Alias, RunletObject<Field, Type, FinexClass>>> singleEmptyRow = new LinkedList<Map<Alias, RunletObject<Field, Type, FinexClass>>>(); singleEmptyRow.add(new HashMap<Alias, RunletObject<Field, Type, FinexClass>>()); List<Map<Alias, RunletObject<Field, Type, FinexClass>>> cartesianProduct = getNodeSet( singleEmptyRow, oql.getFromClauses(), interpreter, cache); List<Map<Alias, RunletObject<Field, Type, FinexClass>>> filteredByWhere = filtereByWhereClause(cartesianProduct, interpreter); RunletObject<Field, Type, FinexClass> result = createTuples(filteredByWhere, interpreter); return result; } /** * For each node map in the <tt>context</tt> (representing a "row" under construction) compute the first of the * <tt>remainingFromClauses</tt> expressions. Cache the results for the expression by the set of nodes from the * <tt>context</tt> for those aliases used by the <tt>from</tt> expression. If the cache already contains those results, use * them. * <p> * * For each of the <tt>from</tt> results construct a new map that extends the current <tt>context</tt> map by an alias entry * with a node for the current <tt>from</tt> result. * <p> * * If there are further <tt>from</tt> clauses, recurse, using the new context that contains the computed aliases for the * current <tt>from</tt> clause and return the result. Otherwise, the new context is the result because its list contains maps * that each contain values for all aliases. * <p> * * @param context * a set of "rows" such that all aliases defined by all <tt>from</tt> clauses left of the <tt>from</tt> clause that * is the first in <tt>remainingFromClauses</tt> are defined in there. * * @param remainingFromClauses * one or more expressions, each defining an alias, representing a tail of the list of <tt>from</tt> clauses of an * {@link OqlQuery}. The <tt>from</tt> expressions left of this tail have already been evaluated with the evaluation * results being stored in <tt>context</tt>. * @param cache */ private List<Map<Alias, RunletObject<Field, Type, FinexClass>>> getNodeSet( List<Map<Alias, RunletObject<Field, Type, FinexClass>>> context, List<FromClause> remainingFromClauses, FinexInterpreter interpreter, Map<Pair<Expression, Set<RunletObject<Field, Type, FinexClass>>>, RunletObject<Field, Type, FinexClass>> cache) throws SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { FromClause currentFromClause = remainingFromClauses.get(0); Expression currentFromClauseExpression = currentFromClause.getFromExpression(); Collection<Alias> fromExpDependsOnAliases = getDependsOnAliases(currentFromClauseExpression); List<Map<Alias, RunletObject<Field, Type, FinexClass>>> nextContextOrResult = new LinkedList<Map<Alias, RunletObject<Field, Type, FinexClass>>>(); for (Map<Alias, RunletObject<Field, Type, FinexClass>> contextRow : context) { Set<RunletObject<Field, Type, FinexClass>> cacheKeySet = new HashSet<RunletObject<Field, Type, FinexClass>>(); for (Alias a : fromExpDependsOnAliases) { cacheKeySet.add(contextRow.get(a)); } RunletObject<Field, Type, FinexClass> from; Pair<Expression, Set<RunletObject<Field, Type, FinexClass>>> cacheKey = new Pair<Expression, Set<RunletObject<Field, Type, FinexClass>>>(currentFromClauseExpression, cacheKeySet); if (cache.containsKey(cacheKey)) { from = cache.get(cacheKey); } else { // compute from expression based on required aliases and put into cache FinexStackFrame newFrame = new FinexStackFrame(interpreter.getCallstack().peek()); for (Alias a : fromExpDependsOnAliases) { newFrame.enterValue(a.getName(), contextRow.get(a)); } interpreter.push(newFrame); try { from = interpreter.evaluate(currentFromClauseExpression); cache.put(cacheKey, from); } finally { interpreter.pop(); } } // Create a node map from the context map with an additional "column" for the currentFromClause's alias // such that one list entry is created for each object in the "from" object (one-level iteration, no flattening). // This is also where inner and outer join are distinguished: for an inner join, an empty result will not // create any row in the widened list. For an outer join, an empty result will create a single entry // where the currentFromClause's alias is set to an EmptyObject. if (!from.isEmpty()) { for (RunletObject<Field, Type, FinexClass> singleFromObject : from) { Map<Alias, RunletObject<Field, Type, FinexClass>> extendedRow = new HashMap<Alias, RunletObject<Field, Type, FinexClass>>(contextRow); extendedRow.put(currentFromClause.getAlias(), singleFromObject); nextContextOrResult.add(extendedRow); } } else { boolean isOuterJoin = false; // TODO make configurable in model if (isOuterJoin) { // for an outer join, if the current from clause evaluates to an empty object, // add a single row with an empty object for the current from clause's alias: Map<Alias, RunletObject<Field, Type, FinexClass>> extendedRow = new HashMap<Alias, RunletObject<Field, Type, FinexClass>>(contextRow); extendedRow.put(currentFromClause.getAlias(), new EmptyObject<Field, FinexClass, Type, FinexClass>( currentFromClauseExpression.getType(), interpreter.getModelAdapter())); nextContextOrResult.add(extendedRow); } } } List<Map<Alias, RunletObject<Field, Type, FinexClass>>> result; if (remainingFromClauses.size() > 1) { result = getNodeSet(nextContextOrResult, remainingFromClauses.subList(1, remainingFromClauses.size()), interpreter, cache); } else { result = nextContextOrResult; } return result; } /** * Determines the aliases on which the <tt>fromExp</tt> depends. May be an empty set, * but always valid (non-<tt>null</tt>). */ private Collection<Alias> getDependsOnAliases(Expression fromExp) { return fromExp.getUsedAliases(); } private List<Map<Alias, RunletObject<Field, Type, FinexClass>>> filtereByWhereClause( List<Map<Alias, RunletObject<Field, Type, FinexClass>>> cartesianProduct, FinexInterpreter interpreter) throws JmiException, SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { List<Map<Alias, RunletObject<Field, Type, FinexClass>>> filteredByWhere; if (oql.getCondition() != null) { filteredByWhere = new LinkedList<Map<Alias, RunletObject<Field, Type, FinexClass>>>(); for (Map<Alias, RunletObject<Field, Type, FinexClass>> row : cartesianProduct) { FinexStackFrame newFrame = new FinexStackFrame(interpreter.getCallstack().peek()); for (Alias a : row.keySet()) { newFrame.enterValue(a.getName(), row.get(a)); } interpreter.push(newFrame); try { boolean condition = (Boolean) ((FinexNativeObject) interpreter.evaluate(oql.getCondition())) .getNativeObject(); if (condition) { filteredByWhere.add(row); } } finally { interpreter.pop(); } } } else { filteredByWhere = cartesianProduct; } return filteredByWhere; } /** * Assembles the result set as a set of tuples according to the {@link OqlQuery#getType()}'s fields. * Result is a set of value objects, each of type {@link OqlQuery#getType()}. */ private RunletObject<Field, Type, FinexClass> createTuples(List<Map<Alias, RunletObject<Field, Type, FinexClass>>> filteredCartesianProduct, FinexInterpreter interpreter) throws JmiException, SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { List<RunletObject<Field, Type, FinexClass>> resultObjects = new ArrayList<RunletObject<Field, Type, FinexClass>>(); FinexStackFrame currentStackFrame = interpreter.getCallstack().peek(); for (Map<Alias, RunletObject<Field, Type, FinexClass>> row : filteredCartesianProduct) { HashMap<Field, Collection<ClassTypedObject<Field, Type, FinexClass>>> fieldValues = new HashMap<Field, Collection<ClassTypedObject<Field, Type, FinexClass>>>(); Set<Field> allFieldsWithDefault = new HashSet<Field>(oql.getType().getFieldsWithDefaultValue()); for (Field fieldWithDefault : allFieldsWithDefault) { try { FinexStackFrame frame = new FinexStackFrame(currentStackFrame); for (Alias a : row.keySet()) { frame.enterValue(a.getName(), row.get(a)); } interpreter.push(frame); RunletObject<Field, Type, FinexClass> value = interpreter.evaluate(fieldWithDefault.getDefaultValue()); Collection<ClassTypedObject<Field, Type, FinexClass>> flattenedValues = new ArrayList<ClassTypedObject<Field, Type, FinexClass>>(); fieldValues.put(fieldWithDefault, flattenedValues); for (RunletObject<Field, Type, FinexClass> vo : value.flatten()) { flattenedValues.add((ClassTypedObject<Field, Type, FinexClass>) vo); } } finally { interpreter.pop(); } } FinexValueObject singleResult = interpreter.createValueObject((FinexClass) oql.getType(), fieldValues); resultObjects.add(singleResult); } return FinexInterpreter.turnIntoObjectOfAppropriateMultiplicity(oql.getType(), interpreter, resultObjects, /* isMany */ true); } }