/** * 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.sql.optimizer.plan.AggregateFunctionExpression; import com.foundationdb.sql.optimizer.plan.AggregateSource; import com.foundationdb.sql.optimizer.plan.BooleanConstantExpression; import com.foundationdb.sql.optimizer.plan.BooleanOperationExpression; import com.foundationdb.sql.optimizer.plan.CastExpression; import com.foundationdb.sql.optimizer.plan.ColumnExpression; import com.foundationdb.sql.optimizer.plan.ColumnDefaultExpression; import com.foundationdb.sql.optimizer.plan.ComparisonCondition; import com.foundationdb.sql.optimizer.plan.ConditionExpression; import com.foundationdb.sql.optimizer.plan.ConstantExpression; import com.foundationdb.sql.optimizer.plan.ExpressionNode; import com.foundationdb.sql.optimizer.plan.FunctionExpression; import com.foundationdb.sql.optimizer.plan.InListCondition; import com.foundationdb.sql.optimizer.plan.IsNullIndexKey; import com.foundationdb.sql.optimizer.plan.ParameterExpression; import com.foundationdb.sql.optimizer.plan.ResolvableExpression; import com.foundationdb.sql.optimizer.plan.RoutineExpression; import com.foundationdb.sql.optimizer.plan.SubqueryExpression; import com.foundationdb.sql.optimizer.plan.CreateAs; import com.foundationdb.sql.optimizer.plan.TableSource; import com.foundationdb.sql.script.ScriptBindingsRoutineTExpression; import com.foundationdb.sql.script.ScriptFunctionJavaRoutineTExpression; import com.foundationdb.sql.server.ServerJavaMethodTExpression; import com.foundationdb.sql.types.CharacterTypeAttributes; import com.foundationdb.ais.model.Column; import com.foundationdb.ais.model.Routine; import com.foundationdb.ais.model.TableName; import com.foundationdb.qp.operator.API; import com.foundationdb.qp.operator.Operator; import com.foundationdb.qp.operator.QueryContext; import com.foundationdb.qp.rowtype.RowType; import com.foundationdb.server.collation.AkCollator; import com.foundationdb.server.error.AkibanInternalException; import com.foundationdb.server.error.NoSuchCastException; import com.foundationdb.server.error.UnsupportedSQLException; import com.foundationdb.server.explain.CompoundExplainer; import com.foundationdb.server.explain.Label; import com.foundationdb.server.explain.PrimitiveExplainer; import com.foundationdb.server.explain.Type; import com.foundationdb.server.types.TAggregator; import com.foundationdb.server.types.TCast; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TComparison; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.TKeyComparable; import com.foundationdb.server.types.TPreptimeValue; import com.foundationdb.server.types.common.types.StringAttribute; import com.foundationdb.server.types.common.types.TString; import com.foundationdb.server.types.service.OverloadResolver; import com.foundationdb.server.types.service.OverloadResolver.OverloadResult; import com.foundationdb.server.types.service.TypesRegistryService; import com.foundationdb.server.types.texpressions.Comparison; import com.foundationdb.server.types.texpressions.TCastExpression; import com.foundationdb.server.types.texpressions.TComparisonExpression; import com.foundationdb.server.types.texpressions.TComparisonExpressionBase; import com.foundationdb.server.types.texpressions.TInExpression; import com.foundationdb.server.types.texpressions.TNullExpression; import com.foundationdb.server.types.texpressions.TPreparedBoundField; import com.foundationdb.server.types.texpressions.TPreparedExpression; import com.foundationdb.server.types.texpressions.TPreparedField; import com.foundationdb.server.types.texpressions.TPreparedFunction; import com.foundationdb.server.types.texpressions.TPreparedLiteral; import com.foundationdb.server.types.texpressions.TPreparedParameter; import com.foundationdb.server.types.texpressions.TValidatedAggregator; import com.foundationdb.server.types.texpressions.TValidatedScalar; import com.foundationdb.server.types.value.UnderlyingType; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.util.SparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.List; class ExpressionAssembler { private static final Logger logger = LoggerFactory.getLogger(ExpressionAssembler.class); private final PlanContext planContext; private final PlanExplainContext explainContext; private final SchemaRulesContext rulesContext; private final TypesRegistryService registryService; private final QueryContext queryContext; public ExpressionAssembler(PlanContext planContext) { this.planContext = planContext; if (planContext instanceof ExplainPlanContext) explainContext = ((ExplainPlanContext)planContext).getExplainContext(); else explainContext = null; rulesContext = (SchemaRulesContext)planContext.getRulesContext(); registryService = rulesContext.getTypesRegistry(); queryContext = planContext.getQueryContext(); } public TPreparedExpression assembleExpression(ExpressionNode node, ColumnExpressionContext columnContext, SubqueryOperatorAssembler subqueryAssembler) { TPreparedExpression possiblyLiteral = tryLiteral(node); if (possiblyLiteral != null) return possiblyLiteral; if (node instanceof ConstantExpression) return literal(((ConstantExpression)node)); else if (node instanceof ColumnExpression) return assembleColumnExpression((ColumnExpression)node, columnContext); else if (node instanceof ParameterExpression) return variable((ParameterExpression)node); else if (node instanceof BooleanOperationExpression) { BooleanOperationExpression bexpr = (BooleanOperationExpression)node; return assembleFunction(bexpr, bexpr.getOperation().getFunctionName(), Arrays.<ExpressionNode>asList(bexpr.getLeft(), bexpr.getRight()), columnContext, subqueryAssembler); } else if (node instanceof CastExpression) return assembleCastExpression((CastExpression)node, columnContext, subqueryAssembler); else if (node instanceof ComparisonCondition) { ComparisonCondition cond = (ComparisonCondition)node; TPreparedExpression left = assembleExpression(cond.getLeft(), columnContext, subqueryAssembler); TPreparedExpression right = assembleExpression(cond.getRight(), columnContext, subqueryAssembler); // never use a collator if we have a KeyComparable AkCollator collator = (cond.getKeyComparable() == null) ? collator(cond, left, right) : null; if (collator != null) return collate(left, cond.getOperation(), right, collator); else return compare(left, cond, right); } else if (node instanceof FunctionExpression) { FunctionExpression funcNode = (FunctionExpression)node; return assembleFunction(funcNode, funcNode.getFunction(), funcNode.getOperands(), columnContext, subqueryAssembler); } else if (node instanceof InListCondition) { InListCondition inList = (InListCondition)node; TPreparedExpression lhs = assembleExpression(inList.getOperand(), columnContext, subqueryAssembler); List<TPreparedExpression> rhs = assembleExpressions(inList.getExpressions(), columnContext, subqueryAssembler); return in(lhs, rhs, inList); } else if (node instanceof RoutineExpression) { RoutineExpression routineNode = (RoutineExpression)node; return assembleRoutine(routineNode, routineNode.getRoutine(), routineNode.getOperands(), columnContext, subqueryAssembler); } else if (node instanceof SubqueryExpression) return subqueryAssembler.assembleSubqueryExpression((SubqueryExpression)node); else if (node instanceof AggregateFunctionExpression) throw new UnsupportedSQLException("Aggregate used as regular function", node.getSQLsource()); else if (node instanceof ColumnDefaultExpression) return assembleColumnDefault(((ColumnDefaultExpression)node).getColumn(), null); else if (node instanceof IsNullIndexKey) return new TNullExpression(node.getType()); else throw new UnsupportedSQLException("Unknown expression", node.getSQLsource()); } private List<TPreparedExpression> assembleExpressions(List<ExpressionNode> expressions, ColumnExpressionContext columnContext, SubqueryOperatorAssembler subqueryAssembler) { List<TPreparedExpression> result = new ArrayList<>(expressions.size()); for (ExpressionNode expr : expressions) { result.add(assembleExpression(expr, columnContext, subqueryAssembler)); } return result; } private TPreparedExpression assembleColumnExpression(ColumnExpression column, ColumnExpressionContext columnContext) { if (column.getTable() instanceof CreateAs) { RowType rowType = columnContext.getRowType((CreateAs)column.getTable()); TPreparedExpression expression = assembleBoundFieldExpression(rowType, OperatorAssembler.CREATE_AS_BINDING_POSITION, column.getPosition()); if (explainContext != null) explainColumnExpression(expression, column); return expression; } ColumnExpressionToIndex currentRow = columnContext.getCurrentRow(); if (currentRow != null) { int fieldIndex = currentRow.getIndex(column); if (fieldIndex >= 0) { TPreparedExpression expression = assembleFieldExpression(currentRow.getRowType(), fieldIndex); if (explainContext != null) explainColumnExpression(expression, column); return expression; } } for (ColumnExpressionToIndex boundRow : columnContext.getBoundRows()) { int fieldIndex = boundRow.getIndex(column); if (fieldIndex >= 0) { int rowIndex = columnContext.getBindingPosition(boundRow); TPreparedExpression expression = assembleBoundFieldExpression(boundRow.getRowType(), rowIndex, fieldIndex); if (explainContext != null) explainColumnExpression(expression, column); return expression; } } if(column.getTable() instanceof TableSource){ RowType rowType = columnContext.getRowType(column.getColumn().getTable().getTableId()); TPreparedExpression expression = assembleBoundFieldExpression(rowType, OperatorAssembler.CREATE_AS_BINDING_POSITION, column.getPosition()); if (explainContext != null) explainColumnExpression(expression, column); return expression; } logger.debug("Did not find {} from {} in {}", new Object[] { column, column.getTable(), columnContext.getBoundRows() }); throw new AkibanInternalException("Column not found " + column); } private TPreparedExpression assembleFunction(ExpressionNode functionNode, String functionName, List<ExpressionNode> argumentNodes, ColumnExpressionContext columnContext, SubqueryOperatorAssembler subqueryAssembler) { List<TPreparedExpression> arguments = assembleExpressions(argumentNodes, columnContext, subqueryAssembler); TValidatedScalar overload; SparseArray<Object> preptimeValues = null; if (functionNode instanceof FunctionExpression) { FunctionExpression fexpr = (FunctionExpression) functionNode; overload = fexpr.getResolved(); preptimeValues = fexpr.getPreptimeValues(); } else if (functionNode instanceof BooleanOperationExpression) { List<TPreptimeValue> inputPreptimeValues = new ArrayList<>(argumentNodes.size()); for (ExpressionNode argument : argumentNodes) { inputPreptimeValues.add(argument.getPreptimeValue()); } OverloadResolver<TValidatedScalar> scalarsResolver = registryService.getScalarsResolver(); OverloadResult<TValidatedScalar> overloadResult = scalarsResolver.get(functionName, inputPreptimeValues); overload = overloadResult.getOverload(); } else { throw new AssertionError(functionNode); } TInstance resultInstance = functionNode.getType(); return new TPreparedFunction(overload, resultInstance, arguments, preptimeValues); } private TPreparedExpression assembleCastExpression(CastExpression castExpression, ColumnExpressionContext columnContext, SubqueryOperatorAssembler subqueryAssembler) { TInstance toType = castExpression.getType(); TPreparedExpression expr = assembleExpression(castExpression.getOperand(), columnContext, subqueryAssembler); if (toType == null) return expr; TInstance sourceInstance = expr.resultType(); if (sourceInstance == null) // CAST(NULL as FOOTYPE) { toType = toType.withNullable(true); return new TNullExpression(toType); } else if (!toType.equals(sourceInstance)) { // Do type conversion. TCast tcast = registryService.getCastsResolver().cast(sourceInstance, toType); if (tcast == null) { throw new NoSuchCastException(sourceInstance, toType); } expr = new TCastExpression(expr, tcast, toType); } return expr; } private TPreparedExpression tryLiteral(ExpressionNode node) { TPreparedExpression result = null; TPreptimeValue tpv = node.getPreptimeValue(); if (tpv != null) { TInstance type = tpv.type(); ValueSource value = tpv.value(); if (type != null && value != null) { result = new TPreparedLiteral(type, value); } } return result; } private TPreparedExpression literal(ConstantExpression expression) { TPreptimeValue ptval = expression.getPreptimeValue(); return new TPreparedLiteral(ptval.type(), ptval.value()); } private TPreparedExpression variable(ParameterExpression expression) { return new TPreparedParameter(expression.getPosition(), expression.getType()); } private TPreparedExpression compare(TPreparedExpression left, ComparisonCondition comparison, TPreparedExpression right) { TKeyComparable keyComparable = comparison.getKeyComparable(); if (keyComparable != null) { return new TKeyComparisonPreparation(left, comparison.getOperation(), right, comparison.getKeyComparable()); } else { return new TComparisonExpression(left, comparison.getOperation(), right); } } private TPreparedExpression collate(TPreparedExpression left, Comparison comparison, TPreparedExpression right, AkCollator collator) { return new TComparisonExpression(left, comparison, right, collator); } private AkCollator collator(ComparisonCondition cond, TPreparedExpression left, TPreparedExpression right) { TInstance leftInstance = left.resultType(); TInstance rightInstance = right.resultType(); TClass tClass = leftInstance.typeClass(); assert tClass.compatibleForCompare(rightInstance.typeClass()) : tClass + " != " + rightInstance.typeClass(); if (tClass.underlyingType() != UnderlyingType.STRING) return null; CharacterTypeAttributes leftAttributes = StringAttribute.characterTypeAttributes(leftInstance); CharacterTypeAttributes rightAttributes = StringAttribute.characterTypeAttributes(rightInstance); return TString.mergeAkCollators(leftAttributes, rightAttributes); } private TPreparedExpression in(TPreparedExpression lhs, List<TPreparedExpression> rhs, InListCondition inList) { ComparisonCondition comparison = inList.getComparison(); if (comparison == null) return TInExpression.prepare(lhs, rhs, queryContext); else return TInExpression.prepare(lhs, rhs, comparison.getRight().getType(), comparison.getKeyComparable(), queryContext); } private TPreparedExpression assembleFieldExpression(RowType rowType, int fieldIndex) { return new TPreparedField(rowType.typeAt(fieldIndex), fieldIndex); } private TPreparedExpression assembleBoundFieldExpression(RowType rowType, int rowIndex, int fieldIndex) { return new TPreparedBoundField(rowType, rowIndex, fieldIndex); } private TPreparedExpression assembleRoutine(ExpressionNode routineNode, Routine routine, List<ExpressionNode> operandNodes, ColumnExpressionContext columnContext, SubqueryOperatorAssembler subqueryAssembler) { List<TPreparedExpression> inputs = assembleExpressions(operandNodes, columnContext, subqueryAssembler); switch (routine.getCallingConvention()) { case JAVA: return new ServerJavaMethodTExpression(routine, inputs); case SCRIPT_FUNCTION_JAVA: case SCRIPT_FUNCTION_JSON: return new ScriptFunctionJavaRoutineTExpression(routine, inputs); case SCRIPT_BINDINGS: case SCRIPT_BINDINGS_JSON: return new ScriptBindingsRoutineTExpression(routine, inputs); default: throw new AkibanInternalException("Unimplemented routine " + routine.getName()); } } public Operator assembleAggregates(Operator inputOperator, RowType rowType, int nkeys, AggregateSource aggregateSource) { List<ResolvableExpression<TValidatedAggregator>> aggregates = aggregateSource.getResolved(); int naggrs = aggregates.size(); List<TAggregator> aggregators = new ArrayList<>(naggrs); List<TInstance> outputInstances = new ArrayList<>(naggrs); for (int i = 0; i < naggrs; ++i) { ResolvableExpression<TValidatedAggregator> aggr = aggregates.get(i); aggregators.add(aggr.getResolved()); outputInstances.add(aggr.getType()); } return API.aggregate_Partial( inputOperator, rowType, nkeys, aggregators, outputInstances, aggregateSource.getOptions()); } // Changes here probably need reflected in OnlineHelper#buildColumnDefault() public TPreparedExpression assembleColumnDefault(Column column, TPreparedExpression expression) { return PlanGenerator.generateDefaultExpression(column, expression, registryService, rulesContext.getTypesTranslator(), planContext.getQueryContext()); } public ConstantExpression evalNow(PlanContext planContext, ExpressionNode node) { if (node instanceof ConstantExpression) return (ConstantExpression)node; TPreparedExpression expr = assembleExpression(node, null, null); TPreptimeValue preptimeValue = expr.evaluateConstant(planContext.getQueryContext()); if (preptimeValue == null) throw new AkibanInternalException("required constant expression: " + expr); ValueSource valueSource = preptimeValue.value(); if (valueSource == null) throw new AkibanInternalException("required constant expression: " + expr); if (node instanceof ConditionExpression) { Boolean value = valueSource.isNull() ? null : valueSource.getBoolean(); return new BooleanConstantExpression(value); } else { return new ConstantExpression(preptimeValue); } } private static class TKeyComparisonPreparation extends TComparisonExpressionBase { private TKeyComparisonPreparation(TPreparedExpression left, Comparison op, TPreparedExpression right, TKeyComparable comparable) { super(left, op, right); this.comparison = comparable.getComparison(); TClass leftIn = tClass(left); TClass rightIn = tClass(right); TClass leftCmp = comparable.getLeftTClass(); TClass rightCmp = comparable.getRightTClass(); if (leftIn == leftCmp && rightIn == rightCmp) { reverseComparison = false; } else if (rightIn == leftCmp && leftIn == rightCmp) { reverseComparison = true; } else { throw new IllegalArgumentException( "invalid comparisons: " + left + " and " + right + " against " + comparable); } } private TClass tClass(TPreparedExpression left) { TInstance type = left.resultType(); return type == null ? null : type.typeClass(); } @Override protected int compare(TInstance leftInstance, ValueSource left, TInstance rightInstance, ValueSource right) { return reverseComparison ? - comparison.compare(rightInstance, right, leftInstance, left) : comparison.compare(leftInstance, left, rightInstance, right); } private final TComparison comparison; private final boolean reverseComparison; } private void explainColumnExpression(TPreparedExpression expression, ColumnExpression column) { CompoundExplainer explainer = new CompoundExplainer(Type.EXTRA_INFO); explainer.addAttribute(Label.POSITION, PrimitiveExplainer.getInstance(column.getPosition())); Column aisColumn = column.getColumn(); if (aisColumn != null) { explainer.addAttribute(Label.TABLE_CORRELATION, PrimitiveExplainer.getInstance(column.getTable().getName())); TableName tableName = aisColumn.getTable().getName(); explainer.addAttribute(Label.TABLE_SCHEMA, PrimitiveExplainer.getInstance(tableName.getSchemaName())); explainer.addAttribute(Label.TABLE_NAME, PrimitiveExplainer.getInstance(tableName.getTableName())); explainer.addAttribute(Label.COLUMN_NAME, PrimitiveExplainer.getInstance(aisColumn.getName())); } explainContext.putExtraInfo(expression, explainer); } public interface ColumnExpressionToIndex { /** Return the field position of the given column in the target row. */ public int getIndex(ColumnExpression column); /** Return the row type that the index goes with. */ public RowType getRowType(); } public interface ColumnExpressionContext { /** Get the current input row if any. */ public ColumnExpressionToIndex getCurrentRow(); /** Get list (deepest first) of rows from nested loops. */ public Iterable<ColumnExpressionToIndex> getBoundRows(); /** Get the position associated with the given row. */ public int getBindingPosition(ColumnExpressionToIndex boundRow); public RowType getRowType(CreateAs createAs); public RowType getRowType(int tableID); } public interface SubqueryOperatorAssembler { /** Assemble the given subquery expression. */ public TPreparedExpression assembleSubqueryExpression(SubqueryExpression subqueryExpression); } }