/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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/>. */ package com.foundationdb.sql.optimizer.rule; import com.foundationdb.server.types.service.TypesRegistryService; import com.foundationdb.server.types.value.Value; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.sql.optimizer.plan.*; import com.foundationdb.sql.optimizer.plan.ExpressionsSource.DistinctState; import com.foundationdb.sql.optimizer.rule.TypeResolver.ResolvingVisitor; import com.foundationdb.server.types.texpressions.Comparison; import com.foundationdb.ais.model.Routine; import com.foundationdb.qp.operator.QueryContext; import com.foundationdb.server.types.service.TCastResolver; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TExecutionContext; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.TPreptimeValue; import com.foundationdb.server.types.common.types.TypesTranslator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** Evaluate as much as possible at generate time. * As with any compiler, false constants used in conditions can lead * to dead code, that is, join sources that don't need to bother * outputting any data. And these empty data sets can in turn affect * subqueries and aggregation. */ public class ConstantFolder extends BaseRule { private static final Logger logger = LoggerFactory.getLogger(ConstantFolder.class); public ConstantFolder() { } @Override protected Logger getLogger() { return logger; } @Override public void apply(PlanContext planContext) { Folder folder = new Folder(planContext); while (folder.foldConstants()); folder.finishAggregates(); } public static class Folder implements PlanVisitor, ExpressionRewriteVisitor { protected final PlanContext planContext; protected final ExpressionAssembler expressionAssembler; private Set<ColumnSource> eliminatedSources = new HashSet<>(); private Set<AggregateSource> changedAggregates = null; private enum State { FOLDING, AGGREGATES, FOLDING_PIECEMEAL }; private State state; private boolean changed; private Map<ConditionExpression,Boolean> topLevelConditions = new IdentityHashMap<>(); private ExpressionRewriteVisitor resolvingVisitor; public Folder(PlanContext planContext) { this.planContext = planContext; this.expressionAssembler = new ExpressionAssembler(planContext); } public void initResolvingVisitor(ExpressionRewriteVisitor resolvingVisitor) { this.resolvingVisitor = resolvingVisitor; } public ExpressionNode foldConstants(ExpressionNode fromNode) { do { state = State.FOLDING_PIECEMEAL; changed = false; topLevelConditions.clear(); fromNode = fromNode.accept(this); } while (changed); return fromNode; } /** Return <code>true</code> if substantial enough changes were made that * need to be run again. */ public boolean foldConstants() { state = State.FOLDING; changed = false; topLevelConditions.clear(); planContext.accept(this); return changed; } @Override public boolean visitEnter(PlanNode n) { return visit(n); } @Override public boolean visitLeave(PlanNode n) { if (state == State.FOLDING) { if (n instanceof Select) selectNode((Select)n); else if (n instanceof SubquerySource) subquerySource((SubquerySource)n); else if (n instanceof AggregateSource) aggregateSource((AggregateSource)n); } return true; } @Override public boolean visit(PlanNode n) { if (n instanceof Select) { for (ConditionExpression condition : ((Select)n).getConditions()) { topLevelConditions.put(condition, Boolean.TRUE); } } return true; } @Override public boolean visitChildrenFirst(ExpressionNode expr) { // This assumes that there is no particular advantage to // figuring out the whole expression tree of functions and // constants and passing it to eval as a whole, rather than // doing it piece-by-piece. return true; } @Override public ExpressionNode visit(ExpressionNode expr) { if ((state == State.FOLDING) || (state == State.FOLDING_PIECEMEAL)) { if (expr instanceof ComparisonCondition) return comparisonCondition((ComparisonCondition)expr); else if (expr instanceof CastExpression) return castExpression((CastExpression)expr); else if (expr instanceof FunctionExpression) { if (expr instanceof LogicalFunctionCondition) return logicalFunctionCondition((LogicalFunctionCondition)expr); else return functionExpression((FunctionExpression)expr); } else if (expr instanceof ColumnExpression) return columnExpression((ColumnExpression)expr); else if (expr instanceof SubqueryValueExpression) return subqueryValueExpression((SubqueryValueExpression)expr); else if (expr instanceof ExistsCondition) return existsCondition((ExistsCondition)expr); else if (expr instanceof AnyCondition) return anyCondition((AnyCondition)expr); else if (expr instanceof RoutineExpression) return routineExpression((RoutineExpression)expr); } else if (state == State.AGGREGATES) { if (expr instanceof ColumnExpression) return columnExpression((ColumnExpression)expr); } return expr; } protected ExpressionNode comparisonCondition(ComparisonCondition cond) { Constantness lc = isConstant(cond.getLeft()); Constantness rc = isConstant(cond.getRight()); if ((lc == Constantness.NULL) || (rc == Constantness.NULL)) return newBooleanConstant(null, cond); if ((lc != Constantness.VARIABLE) && (rc != Constantness.VARIABLE)) return evalNow(cond); if (isIdempotentEquality(cond)) { if ((cond.getLeft().getSQLtype() != null) && !cond.getLeft().getSQLtype().isNullable()) { return newBooleanConstant(Boolean.TRUE, cond); } } return cond; } /** @return {@code true} if the given condition is an idempotent equality comparison. */ protected boolean isIdempotentEquality(ComparisonCondition cond) { if (cond.getOperation() != Comparison.EQ) return false; ExpressionNode left = cond.getLeft(); if (!left.equals(cond.getRight())) return false; if ((left instanceof FunctionExpression) && !isIdempotent((FunctionExpression)left)) return false; return true; } protected ExpressionNode castExpression(CastExpression cast) { Constantness c = isConstant(cast.getOperand()); if (c != Constantness.VARIABLE) return evalNow(cast); return cast; } protected ExpressionNode functionExpression(FunctionExpression fun) { String fname = fun.getFunction(); if ("isNull".equals(fname) || "isUnknown".equals(fname)) return isNullExpression(fun); else if ("isTrue".equals(fname)) return isTrueExpression(fun); else if ("isFalse".equals(fname)) return isFalseExpression(fun); else if ("COALESCE".equals(fname) || "ifnull".equals(fname)) return coalesceExpression(fun); else if ("if".equals(fname)) return ifFunction(fun); return genericFunctionExpression(fun); } protected ConditionExpression logicalFunctionCondition(LogicalFunctionCondition lfun) { String fname = lfun.getFunction(); if ("and".equals(fname)) { ConditionExpression left = lfun.getLeft(); ConditionExpression right = lfun.getRight(); if (isConstant(left) == Constantness.CONSTANT) { boolean lv = checkConstantBoolean(left); if (lv) return right; // TRUE AND X -> X else return left; // FALSE AND X -> FALSE } if (isConstant(right) == Constantness.CONSTANT) { boolean rv = checkConstantBoolean(right); if (rv) return left; // X AND TRUE -> X else return right; // X AND FALSE -> FALSE } } else if ("or".equals(fname)) { ConditionExpression left = lfun.getLeft(); ConditionExpression right = lfun.getRight(); if (isConstant(left) == Constantness.CONSTANT) { boolean lv = checkConstantBoolean(left); if (lv) return left; // TRUE OR X -> TRUE else return right; // FALSE OR X -> X } if (isConstant(right) == Constantness.CONSTANT) { boolean rv = checkConstantBoolean(right); if (rv) return right; // X OR TRUE -> TRUE else return left; // X OR FALSE -> X } } else if ("not".equals(fname)) { ConditionExpression cond = lfun.getOperand(); Constantness c = isConstant(cond); if (c == Constantness.NULL) return newBooleanConstant(null, lfun); if (c == Constantness.CONSTANT) return newBooleanConstant(!checkConstantBoolean((ConstantExpression)cond), lfun); } return lfun; } /** @return {@code true} if the given function is invariant for any particular execution of a query. */ protected boolean isIdempotent(FunctionExpression fun) { String fname = fun.getFunction(); // current date/time methods fixed at transaction start. return "current_date".equals(fname) || "current_time".equals(fname) || "current_timestamp".equals(fname); } protected ExpressionNode isNullExpression(FunctionExpression fun) { ExpressionNode operand = fun.getOperands().get(0); if (isConstant(operand) != Constantness.VARIABLE) return evalNow(fun); // pkey IS NULL is FALSE, for instance. if ((operand.getSQLtype() != null) && !operand.getSQLtype().isNullable()) return newBooleanConstant(Boolean.FALSE, fun); return fun; } protected ExpressionNode isTrueExpression(FunctionExpression fun) { ExpressionNode operand = fun.getOperands().get(0); if (isConstant(operand) != Constantness.VARIABLE) return evalNow(fun); else if (isFalseOrUnknown((ConditionExpression)operand)) return newBooleanConstant(Boolean.FALSE, fun); else return fun; } protected ExpressionNode isFalseExpression(FunctionExpression fun) { ExpressionNode operand = fun.getOperands().get(0); if (isConstant(operand) != Constantness.VARIABLE) return evalNow(fun); else if (isTrueOrUnknown((ConditionExpression)operand)) return newBooleanConstant(Boolean.FALSE, fun); else return fun; } protected ExpressionNode coalesceExpression(FunctionExpression fun) { // Don't need all the operands to make progress. List<ExpressionNode> operands = fun.getOperands(); int i = 0; while (i < operands.size()) { ExpressionNode operand = operands.get(i); Constantness c = isConstant(operand); if (c == Constantness.NULL) { operands.remove(i); continue; } if (c == Constantness.CONSTANT) { // If the first arg is a not-null constant, that's the answer. if (i == 0) return operand; } i++; } if (operands.isEmpty()) return newBooleanConstant(null, fun); return fun; } protected ExpressionNode ifFunction(FunctionExpression fun) { List<ExpressionNode> operands = fun.getOperands(); Constantness c = isConstant(operands.get(0)); if (c != Constantness.VARIABLE) { if (checkConstantBoolean(operands.get(0))) return operands.get(1); else return operands.get(2); } return fun; } protected ExpressionNode columnExpression(ColumnExpression col) { ColumnSource source = col.getTable(); if (source instanceof AggregateSource) { AggregateSource asource = (AggregateSource)source; int apos = col.getPosition() - asource.getGroupBy().size(); if (apos >= 0) { List<AggregateFunctionExpression> afuns = asource.getAggregates(); AggregateFunctionExpression afun = afuns.get(apos); if (state == State.FOLDING) { boolean ok = eliminatedSources.contains(asource); if (!ok) { if (isAggregateOfNull(afun)) { ok = true; changedAggregates = new HashSet<>(); changedAggregates.add(asource); } } if (ok) { // This is an aggregate of a NULL value or with no inputs. // That can be NULL or 0 for COUNT. Object value = null; if (isAggregateZero(afun)) value = Long.valueOf(0); return newConstant(value, col); } } else if (state == State.AGGREGATES) { if (changedAggregates.contains(asource) && !eliminatedSources.contains(source)) { // Adjust position for any null functions // which are about to get // removed. References to these positions // were replaced by constants some time // ago. int delta = 0; for (int i = 0; i < apos; i++) { if (isAggregateOfNull(afuns.get(i))) { delta++; } } if (delta > 0) col.setPosition(col.getPosition() - delta); } } } } if (state == State.FOLDING) { if (eliminatedSources.contains(source)) // TODO: Could do a new ColumnExpression with the // NullSource that replaced it, but then that'd have // to eval specially. return newConstant(null, col); } return col; } protected ExpressionNode routineExpression(RoutineExpression expr) { Routine routine = expr.getRoutine(); boolean allConstant = true, anyNull = false; for (ExpressionNode operand : expr.getOperands()) { switch (isConstant(operand)) { case NULL: anyNull = true; /* falls through */ case VARIABLE: allConstant = false; break; } } if (allConstant && routine.isDeterministic()) return evalNow(expr); if (anyNull && !routine.isCalledOnNullInput()) { return newBooleanConstant(null, expr); } return expr; } protected void selectNode(Select select) { boolean keep = checkConditions(select.getConditions()); if (keep && (select.getInput() instanceof Joinable)) { Joinable input = (Joinable)select.getInput(); input = checkOuterJoins(input); if (input == null) keep = false; else select.setInput(input); } if (!keep) { eliminateSources(select.getInput()); PlanNode toReplace = select; PlanWithInput inOutput = toReplace.getOutput(); if (inOutput instanceof Sort) { toReplace = inOutput; inOutput = toReplace.getOutput(); } boolean emptyRow = false; if (inOutput instanceof AggregateSource) { toReplace = inOutput; inOutput = toReplace.getOutput(); eliminatedSources.add((ColumnSource)toReplace); // No GROUP BY outputs an answer for no inputs. emptyRow = ((AggregateSource)toReplace).getGroupBy().isEmpty(); } PlanNode replacement; if (!emptyRow) replacement = new NullSource(); else { // set iTinstance types? No. replacement = new ExpressionsSource(Collections.singletonList(Collections.<ExpressionNode>emptyList())); ResolvingVisitor visitor = (ResolvingVisitor) TypeResolver.getResolver(planContext); if (visitor != null) visitor.visitLeave(replacement); } inOutput.replaceInput(toReplace, replacement); } } protected Joinable checkOuterJoins(Joinable joinable) { if (eliminatedSources.contains(joinable)) return null; if (joinable instanceof JoinNode) { JoinNode join = (JoinNode)joinable; Joinable left = checkOuterJoins(join.getLeft()); Joinable right = checkOuterJoins(join.getRight()); if (!checkConditions(join.getJoinConditions())) { // Join cannot be satified. switch (join.getJoinType()) { case INNER: return null; case LEFT: eliminateSources(right); return left; case RIGHT: eliminateSources(left); return right; } } if (left == null) return right; if (right == null) return left; join.setLeft(left); join.setRight(right); } return joinable; } protected void eliminateSources(PlanNode node) { if (node instanceof ColumnSource) { eliminateSource((ColumnSource)node); } if (node instanceof BasePlanWithInput) { eliminateSources(((BasePlanWithInput)node).getInput()); } else if (node instanceof JoinNode) { JoinNode join = (JoinNode)node; eliminateSources(join.getLeft()); eliminateSources(join.getRight()); } } protected void eliminateSource(ColumnSource source) { eliminatedSources.add(source); // Need to find all the references to it, which means another pass. changed = true; } /** Returns <code>false</code> if it's impossible for these * conditions to be satisfied. * Only valid when conditions are being tested, not when used * as a value. */ protected boolean checkConditions(ConditionList conditions) { if (conditions == null) return true; int i = 0; while (i < conditions.size()) { ConditionExpression condition = conditions.get(i); Constantness c = isConstant(condition); if (c != Constantness.VARIABLE) { if (checkConstantBoolean(condition)) conditions.remove(i); else return false; } else if (isFalseOrUnknown(condition)) return false; else if (condition instanceof LogicalFunctionCondition) { // A complex boolean may have been reduced to a top-level AND. LogicalFunctionCondition lcond = (LogicalFunctionCondition)condition; if ("and".equals(lcond.getFunction())) { conditions.set(i, lcond.getLeft()); conditions.add(i+1, lcond.getRight()); continue; // Might be AND(AND(... } } i++; } return true; } /** Returns <code>true</code> if the given expression will always evaluate * to either <i>true</i> or <i>unknown</i>. */ protected boolean isTrueOrUnknown(ConditionExpression expr) { if (expr instanceof ConstantExpression) { Boolean value = getBooleanObject((ConstantExpression)expr); return ((value == null) || (value == Boolean.TRUE)); } else if (expr instanceof LogicalFunctionCondition) { LogicalFunctionCondition lfun = (LogicalFunctionCondition)expr; String fname = lfun.getFunction(); if ("and".equals(fname)) { return (isTrueOrUnknown(lfun.getLeft()) && isTrueOrUnknown(lfun.getRight())); } else if ("or".equals(fname)) { return (isTrueOrUnknown(lfun.getLeft()) || isTrueOrUnknown(lfun.getRight())); } else if ("not".equals(fname)) { return isFalseOrUnknown(lfun.getOperand()); } } else if (expr instanceof ComparisonCondition) { return isIdempotentEquality((ComparisonCondition)expr); } return false; } /** Returns <code>true</code> if the given expression will always evaluate * to either <i>false</i> or <i>unknown</i>. */ protected boolean isFalseOrUnknown(ConditionExpression expr) { if (expr instanceof ConstantExpression) { Boolean value = getBooleanObject((ConstantExpression)expr); return ((value == null) || (value == Boolean.FALSE)); } else if (expr instanceof LogicalFunctionCondition) { LogicalFunctionCondition lfun = (LogicalFunctionCondition)expr; String fname = lfun.getFunction(); if ("and".equals(fname)) { return (isFalseOrUnknown(lfun.getLeft()) || isFalseOrUnknown(lfun.getRight())); } else if ("or".equals(fname)) { return (isFalseOrUnknown(lfun.getLeft()) && isFalseOrUnknown(lfun.getRight())); } else if ("not".equals(fname)) { return isTrueOrUnknown(lfun.getOperand()); } } else if (expr instanceof AnyCondition) { ExpressionNode inner = getSubqueryColumn(((AnyCondition)expr).getSubquery()); if (inner instanceof ConditionExpression) // Will be false if empty and whatever this is otherwise. // E.g., NULL IN (SELECT ...) return isFalseOrUnknown((ConditionExpression)inner); } return false; } protected boolean isTopLevelCondition(ConditionExpression cond) { return (topLevelConditions.get(cond) == Boolean.TRUE); } protected ExpressionNode subqueryValueExpression(SubqueryValueExpression expr) { SubqueryEmptiness empty = isEmptySubquery(expr.getSubquery()); if (empty == SubqueryEmptiness.EMPTY) { return newConstant(null, expr); } ExpressionNode inner = getSubqueryColumn(expr.getSubquery()); if (inner != null) { Constantness ic = isConstant(inner); if (ic == Constantness.NULL) { // If it's empty, it's NULL. // If it selects something, that projects NULL. // NULL either way. return newConstant(null, expr); } else if ((ic == Constantness.CONSTANT) && (empty == SubqueryEmptiness.NON_EMPTY)) { // If the inner is a constant and it's driven by a // VALUES, know the value it must generate. It // would be possible to run the whole subquery if // a variable expression only depended on fields // from the VALUES, but that's getting to be a lot // of setup. return inner; } } return expr; } protected ExpressionNode existsCondition(ExistsCondition cond) { if (isEmptySubquery(cond.getSubquery()) == SubqueryEmptiness.EMPTY) { // Empty EXISTS is false. return newBooleanConstant(Boolean.FALSE, cond); } return cond; } protected ExpressionNode anyCondition(AnyCondition cond) { SubqueryEmptiness empty = isEmptySubquery(cond.getSubquery()); if (empty == SubqueryEmptiness.EMPTY) { // Empty ANY is false. return newBooleanConstant(Boolean.FALSE, cond); } ExpressionNode inner = getSubqueryColumn(cond.getSubquery()); if ((inner != null) && (isConstant(inner) == Constantness.CONSTANT) && ((empty == SubqueryEmptiness.NON_EMPTY) || (!checkConstantBoolean(inner)))) { // Constant false: if it's empty, it's false. If it // selects something, that projects false. False // either way. // Constant true: if it's known non-empty, that's what // is returned. return inner; } InCondition in = InCondition.of(cond, planContext); if (in != null) { in.dedup(isTopLevelCondition(cond), (state == State.FOLDING)); switch (in.compareConstants(this)) { case COMPARE_NULL: return newBooleanConstant(null, cond); case ROW_EQUALS: return newBooleanConstant(Boolean.TRUE, cond); } if (in.isEmpty()) return newBooleanConstant(Boolean.FALSE, cond); if (in.isSingleton()) { return in.buildCondition(in.getSingleton(), this); } } return cond; } protected void subquerySource(SubquerySource source) { if (isEmptySubquery(source.getSubquery()) == SubqueryEmptiness.EMPTY) { eliminateSource(source); } } static enum SubqueryEmptiness { UNKNOWN, EMPTY, NON_EMPTY } protected SubqueryEmptiness isEmptySubquery(Subquery subquery) { PlanNode node = subquery.getQuery(); if (node instanceof ResultSet) node = ((ResultSet)node).getInput(); if (node instanceof Project) node = ((Project)node).getInput(); if ((node instanceof Select) && ((Select)node).getConditions().isEmpty()) node = ((BasePlanWithInput)node).getInput(); if (node instanceof NullSource) return SubqueryEmptiness.EMPTY; if (node instanceof ExpressionsSource) { int nrows = ((ExpressionsSource)node).getExpressions().size(); if (nrows == 0) return SubqueryEmptiness.EMPTY; else return SubqueryEmptiness.NON_EMPTY; } else return SubqueryEmptiness.UNKNOWN; } // If the inside of this subquery returns a single column (in // an obvious to work out way), get it. protected ExpressionNode getSubqueryColumn(Subquery subquery) { PlanNode node = subquery.getQuery(); if (node instanceof ResultSet) node = ((ResultSet)node).getInput(); if (node instanceof Project) { List<ExpressionNode> cols = ((Project)node).getFields(); if (cols.size() == 1) return cols.get(0); } return null; } public void finishAggregates() { if (changedAggregates == null) return; state = State.AGGREGATES; planContext.accept(this); // Now that all the indexes are fixed, we can finally // remove the precomputed aggregate results. for (AggregateSource asource : changedAggregates) { List<AggregateFunctionExpression> afuns = asource.getAggregates(); int i = 0; while (i < afuns.size()) { AggregateFunctionExpression afun = afuns.get(i); if (isAggregateOfNull(afun)) { afuns.remove(i); continue; } i++; } } } protected void aggregateSource(AggregateSource aggr) { // TODO: Check nullity of outer join result. Should be // added even if not on column. for (AggregateFunctionExpression afun : aggr.getAggregates()) { if ((afun.getOperand() != null) && !afun.isDistinct() && "COUNT".equals(afun.getFunction()) && ((isConstant(afun.getOperand()) == Constantness.CONSTANT) || ((afun.getOperand().getSQLtype() != null) && !afun.getOperand().getSQLtype().isNullable()))) { // COUNT(constant or NOT NULL) -> COUNT(*). afun.setOperand(null); } } } protected boolean isAggregateOfNull(AggregateFunctionExpression afun) { return ((afun.getOperand() != null) && (isConstant(afun.getOperand()) == Constantness.NULL)); } protected boolean isAggregateZero(AggregateFunctionExpression afun) { return ("COUNT".equals(afun.getFunction())); } protected static enum Constantness { VARIABLE, CONSTANT, NULL } protected ExpressionNode evalNow(ExpressionNode node) { return expressionAssembler.evalNow(planContext, node); } protected boolean checkConstantBoolean(ExpressionNode node) { Boolean actual = getBooleanObject((ConstantExpression)node); return Boolean.TRUE.equals(actual); } protected ConditionExpression newBooleanConstant(Boolean value, ExpressionNode source) { return (ConditionExpression) newExpression(new BooleanConstantExpression(value)); } protected ExpressionNode newExpression(ExpressionNode expr) { if (resolvingVisitor != null) return resolvingVisitor.visit(expr); else return expr; } protected ExpressionNode newConstant(Object value, ExpressionNode source) { return (value == null) ? newExpression(ConstantExpression.typedNull( source.getSQLtype(), source.getSQLsource(), source.getType())) : newExpression(new ConstantExpression(value, source.getSQLtype(), source.getSQLsource(), source.getType())); } protected ExpressionNode genericFunctionExpression(FunctionExpression fun) { TPreptimeValue preptimeValue = fun.getPreptimeValue(); if (preptimeValue.value() == null) return fun; if (fun instanceof FunctionCondition) { return newBooleanConstant(getBooleanObject(preptimeValue), fun); } return new ConstantExpression(preptimeValue); } protected Boolean getBooleanObject(ConstantExpression expression) { return getBooleanObject(expression.getPreptimeValue()); } protected Boolean getBooleanObject(TPreptimeValue preptimeValue) { ValueSource value = preptimeValue.value(); return value == null || value.isNull() ? null : value.getBoolean(); } protected Constantness isConstant(ExpressionNode expr) { TPreptimeValue tpv = expr.getPreptimeValue(); ValueSource value = tpv.value(); if (tpv.type() == null) { assert value == null || value.isNull() : value; assert !(expr instanceof ParameterExpression) : value; return Constantness.NULL; } if (value == null) return Constantness.VARIABLE; return value.isNull() ? Constantness.NULL : Constantness.CONSTANT; } } // Recognize and improve IN conditions with a list (or VALUES). protected static class InCondition implements Comparator<List<ExpressionNode>> { private AnyCondition any; private ExpressionsSource expressions; private List<ComparisonCondition> comparisons; private Project project; private final TypesRegistryService typesRegistry; private final TypesTranslator typesTranslator; private final QueryContext qc; private InCondition(AnyCondition any, ExpressionsSource expressions, List<ComparisonCondition> comparisons, Project project, PlanContext planContext) { this.any = any; this.expressions = expressions; this.comparisons = comparisons; this.project = project; SchemaRulesContext rulesContext = (SchemaRulesContext)planContext.getRulesContext(); typesRegistry = rulesContext.getTypesRegistry(); typesTranslator = rulesContext.getTypesTranslator(); qc = planContext.getQueryContext(); } public boolean isEmpty() { return expressions.getExpressions().isEmpty(); } public boolean isSingleton() { return (expressions.getExpressions().size() == 1); } public List<ExpressionNode> getSingleton() { return expressions.getExpressions().get(0); } public List<ComparisonCondition> getConditions() { return comparisons; } // Recognize the form of IN we support improving. public static InCondition of(AnyCondition any, PlanContext planContext) { Subquery subquery = any.getSubquery(); PlanNode input = subquery.getInput(); if (!(input instanceof Project)) return null; Project project = (Project)input; input = project.getInput(); if (!(input instanceof ExpressionsSource)) return null; ExpressionsSource expressions = (ExpressionsSource)input; if (project.getFields().size() != 1) return null; ExpressionNode cond = project.getFields().get(0); if (!(cond instanceof ConditionExpression)) return null; List<ComparisonCondition> comps = new ArrayList<>(); if (!getAnyConditions(comps, (ConditionExpression)cond, expressions)) return null; List<List<ExpressionNode>> rows = expressions.getExpressions(); if (!(rows.isEmpty() || (rows.get(0).size() == comps.size()))) return null; return new InCondition(any, expressions, comps, project, planContext); } private static boolean getAnyConditions(List<ComparisonCondition> comps, ConditionExpression cond, ExpressionsSource expressions) { if (cond instanceof ComparisonCondition) { ComparisonCondition comp = (ComparisonCondition)cond; if (!(comp.getRight().isColumn() && (comp.getOperation() == Comparison.EQ) && (((ColumnExpression)comp.getRight()).getTable() == expressions) && (((ColumnExpression)comp.getRight()).getPosition() == comps.size()))) return false; comps.add((ComparisonCondition)cond); return true; } else if (cond instanceof LogicalFunctionCondition) { LogicalFunctionCondition lcond = (LogicalFunctionCondition)cond; return (getAnyConditions(comps, lcond.getLeft(), expressions) && getAnyConditions(comps, lcond.getRight(), expressions)); } else { return false; } } public void dedup(boolean topLevel, boolean setDistinct) { if (expressions.getDistinctState() != null) return; List<List<ExpressionNode>> rows = expressions.getExpressions(); List<List<ExpressionNode>> constants = new ArrayList<>(); List<List<ExpressionNode>> parameters = null; List<List<ExpressionNode>> others = null; boolean anyNull = false; for (List<ExpressionNode> row : rows) { boolean allConstant = true, allConstOrParam = true, hasNull = false; for (ExpressionNode col : row) { if (col instanceof ConstantExpression) { ConstantExpression constant = (ConstantExpression)col; if (constant.getValue() == null) { anyNull = hasNull = true; } } else { allConstant = false; if (!isParam(col)) { allConstOrParam = false; } } } if (hasNull && topLevel) { // A top-level condition does not need to worry about the // difference between false and unknown and so can discard // NULL elements. continue; } if (allConstant) { constants.add(row); } else if (allConstOrParam) { if (parameters == null) parameters = new ArrayList<>(); parameters.add(row); } else { if (others == null) others = new ArrayList<>(); others.add(row); } } if (constants.size() > 1) Collections.sort(constants, this); rows.clear(); List<ExpressionNode> lastRow = null; for (List<ExpressionNode> row : constants) { if ((lastRow == null) || (compare(lastRow, row) != 0)) { rows.add(row); lastRow = row; } } if (parameters != null) rows.addAll(parameters); if (others != null) rows.addAll(others); if (setDistinct) { DistinctState distinct; if (others != null) distinct = DistinctState.HAS_EXPRESSSIONS; else if (parameters != null) distinct = DistinctState.HAS_PARAMETERS; else if (anyNull) distinct = DistinctState.DISTINCT_WITH_NULL; else distinct = DistinctState.DISTINCT; expressions.setDistinctState(distinct); } } private static boolean isParam(ExpressionNode expr) { // CAST(? AS type) is okay, too. // (Actually, any function with only one param as arguments would be.) while (expr instanceof CastExpression) expr = ((CastExpression)expr).getOperand(); return (expr instanceof ParameterExpression); } public enum CompareConstants { NORMAL, COMPARE_NULL, ROW_EQUALS }; /** * * For types3 ONLY! * * Compare ValueSources of two constant expression node * @param leftNode * @param rightNode * @param registry * @param qc * @return true if the two ExpressionNodes' ValueSource are equal. * false otherwise */ public static boolean comparePrepValues(ExpressionNode leftNode, ExpressionNode rightNode, TypesRegistryService registry, TypesTranslator typesTranslator, QueryContext qc) { // if either is not constant, preptime values aren't available if (!leftNode.isConstant() || !rightNode.isConstant()) return false; ValueSource leftSource = leftNode.getPreptimeValue().value(); ValueSource rightSource = rightNode.getPreptimeValue().value(); TInstance lTIns = leftSource.getType(); TInstance rTIns = rightSource.getType(); if (TClass.comparisonNeedsCasting(lTIns, rTIns)) { boolean nullable = leftSource.isNull() || rightSource.isNull(); TCastResolver casts = registry.getCastsResolver(); TInstance common = TypeResolver.commonInstance(casts, lTIns, rTIns); if (common == null) common = typesTranslator.typeForString(); Value leftCasted = new Value(common); Value rightCasted = new Value(common); TExecutionContext execContext = new TExecutionContext(Arrays.asList(lTIns, rTIns), common, qc); casts.cast(lTIns, common).evaluate(execContext, leftSource, leftCasted); casts.cast(rTIns, common).evaluate(execContext, rightSource, rightCasted); return TClass.compare(leftCasted.getType(), leftCasted, rightCasted.getType(),rightCasted) == 0; } else return TClass.compare(lTIns, leftSource, rTIns, rightSource) == 0; } // If some of the values in the LHS row and RHS rows are constants, // can compare them now, and either eliminate a LHS value that always matches // or a row that never matches or find a row that always matches, which is // the answer. public CompareConstants compareConstants(Folder folder) { List<List<ExpressionNode>> rows = expressions.getExpressions(); BitSet matching = new BitSet(rows.size()); matching.set(0, rows.size()); boolean removedRow = false, removedComparison = false; int i = 0; while (i < comparisons.size()) { ComparisonCondition cond = comparisons.get(i); ExpressionNode left = cond.getLeft(); switch (folder.isConstant(left)) { case NULL: return CompareConstants.COMPARE_NULL; case CONSTANT: { boolean verticalMatch = true; for (int j = 0; j < rows.size(); j++) { List<ExpressionNode> row = rows.get(j); if (row == null) continue; ExpressionNode right = row.get(i); if (folder.isConstant(right) == Folder.Constantness.CONSTANT) { if (!comparePrepValues(left, right, typesRegistry, typesTranslator, qc)) { // Definitely not equal, can remove row. rows.set(j, null); removedRow = true; matching.clear(j); } continue; // Definitely equal. } // Neither this row nor this column known compare equal. verticalMatch = false; matching.clear(j); } if (verticalMatch) { // Column was all constants: every row // matched or was eliminated; don't need this comparison // any more, // This cannot introduce any duplicates, because all the // rows that remain have the same value in the column // being removed and thus would have already been // duplicates. comparisons.remove(i); for (int j = 0; j < rows.size(); j++) { List<ExpressionNode> row = rows.get(j); if (row == null) continue; row.remove(i); } removedComparison = true; continue; } } break; default: matching.clear(); } i++; } if (!matching.isEmpty()) return CompareConstants.ROW_EQUALS; if (removedRow) { int j = 0; while (j < rows.size()) { if (rows.get(j) == null) rows.remove(j); else j++; } } if (removedComparison) project.getFields().set(0, buildCondition(null, folder)); return CompareConstants.NORMAL; } public ConditionExpression buildCondition(List<ExpressionNode> values, Folder folder) { ConditionExpression result = null; for (int i = 0; i < comparisons.size(); i++) { ComparisonCondition comp = comparisons.get(i); if (values != null) comp.setRight(values.get(i)); if (result == null) result = comp; else { List<ConditionExpression> operands = new ArrayList<>(2); operands.add(result); operands.add(comp); result = (ConditionExpression) folder.newExpression(new LogicalFunctionCondition("and", operands, comp.getSQLtype(), null, comp.getType())); } } if (result == null) result = folder.newBooleanConstant(Boolean.TRUE, any); return result; } @Override @SuppressWarnings("unchecked") public int compare(List<ExpressionNode> r1, List<ExpressionNode> r2) { for (int i = 0; i < r1.size(); i++) { Comparable o1 = asComparable(r1.get(i)); Comparable o2 = asComparable(r2.get(i)); if (o1 == null) { if (o2 != null) { return +1; } } else if (o2 == null) { return -1; } else { int c = o1.compareTo(o2); if (c != 0) return c; } } return 0; } private Comparable asComparable(ExpressionNode elem) { // TODO Needed because a TKeyComparison may cause the expressions to not be cast to the same type. // This will only work as long as all TKeyComparisons are between integer types. Object value = ((ConstantExpression)elem).getValue(); assert value == null || value instanceof Comparable : "value not a comparable: " + value.getClass(); Comparable result = (Comparable)value; if (result instanceof Byte || result instanceof Short || result instanceof Integer || result instanceof Long) result = ((Number)result).longValue(); return result; } } }