package com.tesora.dve.sql.transform.strategy; /* * #%L * Tesora Inc. * Database Virtualization Engine * %% * Copyright (C) 2011 - 2014 Tesora Inc. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.util.Collections; import java.util.concurrent.ThreadLocalRandom; import org.apache.commons.lang.StringEscapeUtils; import com.tesora.dve.common.PEStringUtils; import com.tesora.dve.db.DBNative; import com.tesora.dve.exceptions.PEException; import com.tesora.dve.resultset.ColumnMetadata; import com.tesora.dve.resultset.ColumnSet; import com.tesora.dve.resultset.IntermediateResultSet; import com.tesora.dve.resultset.ProjectionInfo; import com.tesora.dve.resultset.ResultRow; import com.tesora.dve.sql.expression.ExpressionUtils; import com.tesora.dve.sql.node.Edge; import com.tesora.dve.sql.node.LanguageNode; import com.tesora.dve.sql.node.Traversal; import com.tesora.dve.sql.node.expression.ActualLiteralExpression; import com.tesora.dve.sql.node.expression.ExpressionAlias; import com.tesora.dve.sql.node.expression.ExpressionNode; import com.tesora.dve.sql.node.expression.FunctionCall; import com.tesora.dve.sql.node.expression.LiteralExpression; import com.tesora.dve.sql.node.expression.RandFunctionCall; import com.tesora.dve.sql.node.expression.VariableInstance; import com.tesora.dve.sql.node.structural.LimitSpecification; import com.tesora.dve.sql.node.test.EngineConstant; import com.tesora.dve.sql.node.test.EngineToken; import com.tesora.dve.sql.parser.TokenTypes; import com.tesora.dve.sql.schema.DistributionVector; import com.tesora.dve.sql.schema.DistributionVector.Model; import com.tesora.dve.sql.schema.PEColumn; import com.tesora.dve.sql.schema.SchemaContext; import com.tesora.dve.sql.statement.dml.DMLStatement; import com.tesora.dve.sql.statement.dml.ProjectingStatement; import com.tesora.dve.sql.statement.dml.SelectStatement; import com.tesora.dve.sql.transform.CopyVisitor; import com.tesora.dve.sql.transform.behaviors.defaults.DefaultFeaturePlannerFilter; import com.tesora.dve.sql.transform.behaviors.defaults.DefaultFeatureStepBuilder; import com.tesora.dve.sql.transform.execution.AdhocResultsSessionStep; import com.tesora.dve.sql.transform.execution.DMLExplainReason; import com.tesora.dve.sql.transform.execution.DMLExplainRecord; import com.tesora.dve.sql.transform.execution.ExecutionSequence; import com.tesora.dve.sql.transform.strategy.featureplan.FeatureStep; import com.tesora.dve.sql.transform.strategy.featureplan.NonDMLFeatureStep; import com.tesora.dve.sql.util.ListSet; import com.tesora.dve.variables.AbstractVariableAccessor; /* * Applies when variables are present in the query. If found, we sub in the current values of * them and plan as normal. If there are not persistent tables in the query, and there are no functions * we may attempt to build the uresult set ourselves. * * This transform also handles the rand function. */ public class SessionRewriteTransformFactory extends TransformFactory { private static boolean hasFunctionCall(final DMLStatement stmt, final EngineToken function, final SchemaContext sc) { final ListSet<FunctionCall> functionCalls = EngineConstant.FUNCTIONS.getValue(stmt, sc); for (final FunctionCall call : functionCalls) { if (EngineConstant.FUNCTION.has(call, function)) { return true; } } return false; } private boolean applies(SchemaContext sc, DMLStatement stmt) throws PEException { return EngineConstant.VARIABLES.hasValue(stmt,sc) || !EngineConstant.TABLES_INC_NESTED.hasValue(stmt, sc) || hasFunctionCall(stmt, EngineConstant.RAND, sc); } @Override public FeaturePlannerIdentifier getFeaturePlannerID() { return FeaturePlannerIdentifier.SESSION; } private static final DMLExplainRecord tooComplexExplain = DMLExplainReason.SESSION_TOO_COMPLEX.makeRecord(); private void rewriteUserDefinedVariables(final DMLStatement stmt, final SchemaContext sc) throws PEException { final ListSet<VariableInstance> vis = EngineConstant.VARIABLES.getValue(stmt, sc); for (final VariableInstance vi : vis) { if (vi.getScope().isUserScope() && DBNative.DVE_SITENAME_VAR.equals(vi.getVariableName().get().toLowerCase())) continue; final Edge<?, ExpressionNode> parentEdge = vi.getParentEdge(); final AbstractVariableAccessor va = vi.buildAccessor(sc); final String variableValue = sc.getConnection().getVariableValue(va); if (variableValue != null) { parentEdge.set(LiteralExpression.makeStringLiteral(PEStringUtils.dequote(variableValue))); } else { parentEdge.set(ExpressionUtils.buildNullBinaryCast()); } } } private void rewriteFunctionCalls(final DMLStatement stmt, final SchemaContext sc) { final ListSet<FunctionCall> functionCalls = EngineConstant.FUNCTIONS.getValue(stmt, sc); for (final FunctionCall call : functionCalls) { if (EngineConstant.FUNCTION.has(call, EngineConstant.RAND)) { rewriteRandFunctionCall((RandFunctionCall) call); } } } private void rewriteRandFunctionCall(final RandFunctionCall rand) { if (rand.hasSeed()) { rand.setSeed(new ActualLiteralExpression(rand.getSeed(), TokenTypes.Signed_Integer, rand.getSeed().getSourceLocation(), null)); } else { rand.setSeed(LiteralExpression.makeSignedIntegerLiteral(ThreadLocalRandom.current().nextInt())); } } private static class TooComplexTraversal extends Traversal { private boolean complex = false; public TooComplexTraversal() { super(Order.PREORDER, ExecStyle.ONCE); } public boolean isComplex() { return complex; } public boolean allow(Edge<?,?> e) { return !complex; } public boolean allow(LanguageNode ln) { return !complex; } @Override public LanguageNode action(LanguageNode in) { if (in instanceof LiteralExpression || in instanceof SelectStatement || EngineConstant.VARIABLE.has(in) || in instanceof ExpressionAlias || in instanceof LimitSpecification) { // nothing } else { complex = true; } return in; } } @Override public FeatureStep plan(final DMLStatement stmt, PlannerContext incontext) throws PEException { if (!applies(incontext.getContext(), stmt)) return null; PlannerContext context = incontext.withTransform(getFeaturePlannerID()); DMLStatement copy = CopyVisitor.copy(stmt); // swap in current values of all variables rewriteUserDefinedVariables(copy, context.getContext()); rewriteFunctionCalls(copy, context.getContext()); // complex really means - it's not something like select 1, select @@version_comment - // essentially if we encounter anything that's not a literal or a var, (excluding expr aliases), we have to push down. TooComplexTraversal tca = new TooComplexTraversal(); tca.traverse(copy); if (EngineConstant.TABLES_INC_NESTED.hasValue(copy,context.getContext())) { return buildPlan(copy, context, DefaultFeaturePlannerFilter.INSTANCE); } FeatureStep root = null; if (tca.isComplex() || incontext.getContext().getOptions().isForceSessionPushdown()) { // we must push it down as a regular statement if (copy instanceof ProjectingStatement) { root = DefaultFeatureStepBuilder.INSTANCE.buildProjectingStep(context, this, (ProjectingStatement)copy, new ExecutionCost(false,false,null,-1), context.getContext().getSessionStatementStorageGroup(), context.getContext().getCurrentPEDatabase(false), new DistributionVector(incontext.getContext(), Collections.<PEColumn> emptyList(), Model.BROADCAST), null, tooComplexExplain); } else { root = DefaultFeatureStepBuilder.INSTANCE.buildStep(context, this, copy, null, tooComplexExplain); } } else { // not complex, no children - so we will fulfill this inline final SelectStatement fcopy = (SelectStatement) copy; root = new NonDMLFeatureStep(this, context.getContext().getSessionStatementStorageGroup()) { @Override public void scheduleSelf(PlannerContext pc, ExecutionSequence es) throws PEException { ProjectionInfo projInfo = stmt.getProjectionMetadata(pc.getContext()); ColumnSet md = new ColumnSet(); ResultRow row = new ResultRow(); for(int i = 0; i < fcopy.getProjectionEdge().size(); i++) { ExpressionNode en = fcopy.getProjectionEdge().get(i); if (en instanceof ExpressionAlias) en = ((ExpressionAlias)en).getTarget(); LiteralExpression litex = (LiteralExpression) en; md.addColumn(projInfo.getColumnInfo(i + 1).getName(), 255, "varchar", java.sql.Types.VARCHAR); ColumnMetadata cmd = md.getColumn(i+1); cmd.setAliasName(projInfo.getColumnInfo(i+1).getAlias()); row.addResultColumn(litex.isStringLiteral() ? StringEscapeUtils.unescapeJava((String) litex.getValue(pc.getContext().getValues())) : litex.getValue(pc.getContext().getValues())); } es.append(new AdhocResultsSessionStep(new IntermediateResultSet(md,row))); } }; } return root.withCachingFlag(false); } }