/** * 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.error.UnknownDataTypeException; import com.foundationdb.server.types.*; import com.foundationdb.sql.StandardException; import com.foundationdb.sql.optimizer.*; import com.foundationdb.sql.optimizer.plan.*; import com.foundationdb.sql.optimizer.plan.ExpressionsSource.DistinctState; import com.foundationdb.sql.optimizer.plan.PhysicalSelect.PhysicalResultColumn; import com.foundationdb.sql.optimizer.plan.ResultSet.ResultField; import com.foundationdb.sql.optimizer.plan.Sort.OrderByExpression; import com.foundationdb.sql.optimizer.plan.UpdateStatement.UpdateColumn; import com.foundationdb.sql.optimizer.rule.ExpressionAssembler.ColumnExpressionContext; import com.foundationdb.sql.optimizer.rule.ExpressionAssembler.ColumnExpressionToIndex; import com.foundationdb.sql.optimizer.rule.ExpressionAssembler.SubqueryOperatorAssembler; import com.foundationdb.sql.optimizer.rule.range.ColumnRanges; import com.foundationdb.sql.optimizer.rule.range.RangeSegment; import com.foundationdb.sql.types.DataTypeDescriptor; import com.foundationdb.sql.parser.ParameterNode; import com.foundationdb.ais.model.AkibanInformationSchema; import com.foundationdb.ais.model.Column; import com.foundationdb.ais.model.Group; import com.foundationdb.ais.model.Index; import com.foundationdb.ais.model.IndexColumn; import com.foundationdb.ais.model.Table; import com.foundationdb.ais.model.TableName; import com.foundationdb.qp.operator.API.InputPreservationOption; import com.foundationdb.qp.operator.API.JoinType; import com.foundationdb.server.collation.AkCollator; import com.foundationdb.server.collation.AkCollatorFactory; import com.foundationdb.server.types.service.TypesRegistryService; import com.foundationdb.server.types.common.types.TypesTranslator; import com.foundationdb.server.types.texpressions.AnySubqueryTExpression; import com.foundationdb.server.types.texpressions.ExistsSubqueryTExpression; import com.foundationdb.server.types.texpressions.ResultSetSubqueryTExpression; import com.foundationdb.server.types.texpressions.ScalarSubqueryTExpression; import com.foundationdb.server.types.texpressions.TCastExpression; import com.foundationdb.server.types.texpressions.TNullExpression; import com.foundationdb.server.types.texpressions.TPreparedExpression; import com.foundationdb.server.types.texpressions.TPreparedField; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.server.types.value.Value; import com.foundationdb.server.error.AkibanInternalException; import com.foundationdb.server.error.UnsupportedSQLException; import com.foundationdb.qp.operator.API; import com.foundationdb.qp.operator.API.IntersectOption; import com.foundationdb.qp.operator.IndexScanSelector; import com.foundationdb.qp.operator.Operator; import com.foundationdb.qp.operator.UpdateFunction; import com.foundationdb.qp.row.BindableRow; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.row.ValuesHolderRow; import com.foundationdb.qp.rowtype.*; import com.foundationdb.qp.expression.IndexBound; import com.foundationdb.qp.expression.IndexKeyRange; import com.foundationdb.qp.expression.RowBasedUnboundExpressions; import com.foundationdb.qp.expression.UnboundExpressions; import com.foundationdb.server.service.text.FullTextQueryBuilder; import com.foundationdb.server.service.text.FullTextQueryExpression; import com.foundationdb.server.explain.*; import com.foundationdb.server.api.dml.ColumnSelector; import com.foundationdb.server.api.dml.IndexRowPrefixSelector; import com.foundationdb.util.tap.PointTap; import com.foundationdb.util.tap.Tap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; public class OperatorAssembler extends BaseRule { private static final Logger logger = LoggerFactory.getLogger(OperatorAssembler.class); private static final PointTap SELECT_COUNT = Tap.createCount("sql: select"); private static final PointTap INSERT_COUNT = Tap.createCount("sql: insert"); private static final PointTap UPDATE_COUNT = Tap.createCount("sql: update"); private static final PointTap DELETE_COUNT = Tap.createCount("sql: delete"); public static final int CREATE_AS_BINDING_POSITION = 2; public static final int INSERTION_SORT_MAX_LIMIT = 100; public OperatorAssembler() { } @Override protected Logger getLogger() { return logger; } @Override public void apply(PlanContext plan) { new Assembler(plan).apply(); } static class Assembler implements SubqueryOperatorAssembler { private final PlanContext planContext; private final SchemaRulesContext rulesContext; private final PlanExplainContext explainContext; private final Schema schema; private final ExpressionAssembler expressionAssembler; private final Set<Table> affectedTables; public Assembler(PlanContext planContext) { this.planContext = planContext; rulesContext = (SchemaRulesContext)planContext.getRulesContext(); affectedTables = new HashSet<>(); if (planContext instanceof ExplainPlanContext) explainContext = ((ExplainPlanContext)planContext).getExplainContext(); else explainContext = null; schema = rulesContext.getSchema(); expressionAssembler = new ExpressionAssembler(planContext); initializeBindings(); } public void apply() { planContext.setPlan(assembleStatement((BaseStatement)planContext.getPlan())); } protected BasePlannable assembleStatement(BaseStatement plan) { if (plan instanceof SelectQuery) { SELECT_COUNT.hit(); return selectQuery((SelectQuery)plan); } else if (plan instanceof DMLStatement) { return dmlStatement ((DMLStatement)plan); } else throw new UnsupportedSQLException("Cannot assemble plan: " + plan, null); } protected PhysicalSelect selectQuery(SelectQuery selectQuery) { PlanNode planQuery = selectQuery.getQuery(); RowStream stream = assembleQuery(planQuery); List<PhysicalResultColumn> resultColumns; if (planQuery instanceof ResultSet) { List<ResultField> results = ((ResultSet)planQuery).getFields(); resultColumns = getResultColumns(results); } else { // VALUES results in column1, column2, ... resultColumns = getResultColumns(stream.rowType.nFields()); } if (explainContext != null) explainSelectQuery(stream.operator, selectQuery); List<ParameterNode> parameters = getParameters(); setReturnOutputParameterType(stream, parameters); return new PhysicalSelect(stream.operator, stream.rowType, resultColumns, getParameterTypes(parameters), selectQuery.getCostEstimate(), affectedTables); } private void setReturnOutputParameterType(RowStream stream, List<ParameterNode> parameters) { if (parameters == null) { return; } for (ParameterNode parameter : parameters) { if (parameter.isReturnOutputParam() && parameter.getType() == null) { if (stream.rowType.nFields() > 0) { try { parameter.setType(stream.rowType.typeAt(0).dataTypeDescriptor()); parameter.setUserData(stream.rowType.typeAt(0)); } catch (StandardException e) { // setType should never through a StandardException, but if it does // we might be able to handle not having the type updated. logger.error("ParameterNode.setType threw a StandardException", e); } } else { throw new UnknownDataTypeException(null); } } } } protected void explainSelectQuery(Operator plan, SelectQuery selectQuery) { Attributes atts = new Attributes(); explainCostEstimate(atts, selectQuery.getCostEstimate()); explainContext.putExtraInfo(plan, new CompoundExplainer(Type.EXTRA_INFO, atts)); } protected PhysicalUpdate dmlStatement (DMLStatement statement) { PlanNode planQuery = statement.getInput(); RowStream stream = assembleStream(planQuery); //If we're returning results we need the resultColumns, // including names and types for returning to the user. List<PhysicalResultColumn> resultColumns = null; if (statement.getResultField() != null) { resultColumns = getResultColumns(statement.getResultField()); } // Returning rows, if the table is not null, the insert is returning rows // which need to be passed to the user. boolean returning = (statement.getReturningTable() != null); List<ParameterNode> parameters = getParameters(); setReturnOutputParameterType(stream, parameters); return new PhysicalUpdate(stream.operator, getParameterTypes(parameters), stream.rowType, resultColumns, returning, returning || !isBulkInsert(planQuery), statement.getCostEstimate(), affectedTables); } protected RowStream assembleInsertStatement (InsertStatement insert) { INSERT_COUNT.hit(); PlanNode planQuery = insert.getInput(); List<ExpressionNode> projectFields = null; if (planQuery instanceof Project) { Project project = (Project)planQuery; projectFields = project.getFields(); planQuery = project.getInput(); } RowStream stream = assembleQuery(planQuery); stream = assembleInsertProjectTable (stream, projectFields, insert); stream.operator = API.insert_Returning(stream.operator); if (explainContext != null) explainInsertStatement(stream.operator, insert); return stream; } protected RowStream assembleInsertProjectTable (RowStream input, List<ExpressionNode> projectFields, InsertStatement insert) { TableRowType targetRowType = tableRowType(insert.getTargetTable()); Table table = insert.getTargetTable().getTable(); List<TPreparedExpression> insertsP = null; if (projectFields != null) { // In the common case, we can project into a wider row // of the correct type directly. insertsP = assembleExpressions(projectFields, input.fieldOffsets); } else { // VALUES just needs each field, which will get rearranged below. int nfields = input.rowType.nFields(); insertsP = new ArrayList<>(nfields); for (int i = 0; i < nfields; ++i) { insertsP.add(new TPreparedField(input.rowType.typeAt(i), i)); } } TPreparedExpression[] row = new TPreparedExpression[targetRowType.nFields()]; int ncols = insertsP.size(); for (int i = 0; i < ncols; i++) { Column column = insert.getTargetColumns().get(i); TInstance type = column.getType(); int pos = column.getPosition(); row[pos] = insertsP.get(i); if (!type.equals(row[pos].resultType())) { TypesRegistryService registry = rulesContext.getTypesRegistry(); TCast tcast = registry.getCastsResolver().cast(type.typeClass(), row[pos].resultType().typeClass()); row[pos] = new TCastExpression(row[pos], tcast, type); } } // Fill in column default values for (int i = 0, len = targetRowType.nFields(); i < len; ++i) { Column column = table.getColumnsIncludingInternal().get(i); row[i] = expressionAssembler.assembleColumnDefault(column, row[i]); } insertsP = Arrays.asList(row); // Now complete row. input.operator = API.project_Table(input.operator, input.rowType, targetRowType, insertsP); input.rowType = input.operator.rowType(); input.fieldOffsets = new ColumnSourceFieldOffsets(insert.getTable(), targetRowType); return input; } protected void explainInsertStatement(Operator plan, InsertStatement insertStatement) { Attributes atts = new Attributes(); TableName tableName = insertStatement.getTargetTable().getTable().getName(); atts.put(Label.TABLE_SCHEMA, PrimitiveExplainer.getInstance(tableName.getSchemaName())); atts.put(Label.TABLE_NAME, PrimitiveExplainer.getInstance(tableName.getTableName())); for (Column column : insertStatement.getTargetColumns()) { atts.put(Label.COLUMN_NAME, PrimitiveExplainer.getInstance(column.getName())); } explainContext.putExtraInfo(plan, new CompoundExplainer(Type.EXTRA_INFO, atts)); } protected boolean isBulkInsert(PlanNode planQuery) { if (!(planQuery instanceof InsertStatement)) return false; PlanNode insertSource = ((InsertStatement)planQuery).getInput(); if (!(insertSource instanceof ExpressionsSource)) return false; for (List<ExpressionNode> exprs : ((ExpressionsSource)insertSource).getExpressions()) { if (exprs.isEmpty()) return false; // Must want just generated columns. for (ExpressionNode expr : exprs) { if (!(expr instanceof ConstantExpression)) { return false; } } } return true; } protected RowStream assembleUpdateStatement (UpdateStatement updateStatement) { UPDATE_COUNT.hit(); PlanNode input = updateStatement.getInput(); RowStream stream = assembleQuery(input); TableRowType targetRowType = tableRowType(updateStatement.getTargetTable()); if (input instanceof NullSource) stream.rowType = targetRowType; else assert (stream.rowType == targetRowType) : input; List<UpdateColumn> updateColumns = updateStatement.getUpdateColumns(); List<TPreparedExpression> updatesP = assembleUpdates(targetRowType, updateColumns, stream.fieldOffsets); UpdateFunction updateFunction = new ExpressionRowUpdateFunction(updatesP, targetRowType); stream.operator = API.update_Returning(stream.operator, updateFunction); stream.fieldOffsets = new ColumnSourceFieldOffsets (updateStatement.getTable(), targetRowType); if (explainContext != null) explainUpdateStatement(stream.operator, updateStatement, updateColumns, updatesP); return stream; } protected void explainUpdateStatement(Operator plan, UpdateStatement updateStatement, List<UpdateColumn> updateColumns, List<TPreparedExpression> updatesP) { Attributes atts = new Attributes(); TableName tableName = updateStatement.getTargetTable().getTable().getName(); atts.put(Label.TABLE_SCHEMA, PrimitiveExplainer.getInstance(tableName.getSchemaName())); atts.put(Label.TABLE_NAME, PrimitiveExplainer.getInstance(tableName.getTableName())); for (UpdateColumn column : updateColumns) { atts.put(Label.COLUMN_NAME, PrimitiveExplainer.getInstance(column.getColumn().getName())); atts.put(Label.EXPRESSIONS, updatesP.get(column.getColumn().getPosition()).getExplainer(explainContext)); } explainContext.putExtraInfo(plan, new CompoundExplainer(Type.EXTRA_INFO, atts)); } protected RowStream assembleDeleteStatement (DeleteStatement delete) { DELETE_COUNT.hit(); RowStream stream = assembleQuery(delete.getInput()); TableRowType targetRowType = tableRowType(delete.getTargetTable()); stream.operator = API.delete_Returning(stream.operator, false); stream.fieldOffsets = new ColumnSourceFieldOffsets(delete.getTable(), targetRowType); if (explainContext != null) explainDeleteStatement(stream.operator, delete); return stream; } protected RowStream assembleCreateAsTemp( CreateAs createAs) { RowStream stream = new RowStream(); TableSource tableSource = createAs.getTableSource(); TableRowType rowType = tableRowType(tableSource); List<Value> values = new ArrayList<>(rowType.nFields()); for(int i = 0; i < rowType.nFields(); i++){ values.add(new Value(rowType.typeAt(i))); values.get(i).putNull(); } ValuesHolderRow valuesRow = new ValuesHolderRow(rowType, values); Collection<BindableRow> bindableRows = new ArrayList<>(); bindableRows.add(BindableRow.of(valuesRow)); stream.operator = API.emitBoundRow_Nested( API.valuesScan_Default(bindableRows, rowType), rowType, rowType, rowType, CREATE_AS_BINDING_POSITION); stream.rowType = rowType; return stream; } protected void explainDeleteStatement(Operator plan, DeleteStatement deleteStatement) { Attributes atts = new Attributes(); TableName tableName = deleteStatement.getTargetTable().getTable().getName(); atts.put(Label.TABLE_SCHEMA, PrimitiveExplainer.getInstance(tableName.getSchemaName())); atts.put(Label.TABLE_NAME, PrimitiveExplainer.getInstance(tableName.getTableName())); explainContext.putExtraInfo(plan, new CompoundExplainer(Type.EXTRA_INFO, atts)); } protected RowStream assembleUpdateInput(UpdateInput updateInput) { RowStream stream = assembleQuery(updateInput.getInput()); TableSource table = updateInput.getTable(); TableRowType rowType = tableRowType(table); if ((stream.rowType != rowType) || !boundRowIsForTable(stream.fieldOffsets, table)) { ColumnExpressionToIndex boundRow = lookupNestedBoundRow(table); stream.operator = API.emitBoundRow_Nested(stream.operator, stream.rowType, rowType, boundRow.getRowType(), getBindingPosition(boundRow)); stream.rowType = rowType; stream.fieldOffsets = new ColumnSourceFieldOffsets(table, stream.rowType); } return stream; } // Assemble the top-level query. If there is a ResultSet at // the top, it is not handled here, since its meaning is // different for the different statement types. protected RowStream assembleQuery(PlanNode planQuery) { if (planQuery instanceof ResultSet) planQuery = ((ResultSet)planQuery).getInput(); return assembleStream(planQuery); } // Assemble an ordinary stream node. protected RowStream assembleStream(PlanNode node) { if (node instanceof IndexScan) return assembleIndexScan((IndexScan) node); else if (node instanceof GroupScan) return assembleGroupScan((GroupScan) node); else if (node instanceof Select) return assembleSelect((Select) node); else if (node instanceof Flatten) return assembleFlatten((Flatten) node); else if (node instanceof AncestorLookup) return assembleAncestorLookup((AncestorLookup) node); else if (node instanceof BranchLookup) return assembleBranchLookup((BranchLookup) node); else if (node instanceof MapJoin) return assembleMapJoin((MapJoin) node); else if (node instanceof Product) return assembleProduct((Product) node); else if (node instanceof AggregateSource) return assembleAggregateSource((AggregateSource) node); else if (node instanceof Distinct) return assembleDistinct((Distinct) node); else if (node instanceof Sort) return assembleSort((Sort) node); else if (node instanceof Limit) return assembleLimit((Limit) node); else if (node instanceof NullIfEmpty) return assembleNullIfEmpty((NullIfEmpty) node); else if (node instanceof OnlyIfEmpty) return assembleOnlyIfEmpty((OnlyIfEmpty) node); else if (node instanceof Project) return assembleProject((Project) node); else if (node instanceof ExpressionsSource) return assembleExpressionsSource((ExpressionsSource) node); else if (node instanceof SubquerySource) return assembleSubquerySource((SubquerySource) node); else if (node instanceof NullSource) return assembleNullSource((NullSource) node); else if (node instanceof UsingBloomFilter) return assembleUsingBloomFilter((UsingBloomFilter) node); else if (node instanceof BloomFilterFilter) return assembleBloomFilterFilter((BloomFilterFilter) node); else if (node instanceof UsingHashTable) return assembleUsingHashTable((UsingHashTable)node); else if (node instanceof HashTableLookup) return assembleHashTableLookup((HashTableLookup)node); else if (node instanceof FullTextScan) return assembleFullTextScan((FullTextScan) node); else if (node instanceof InsertStatement) return assembleInsertStatement((InsertStatement)node); else if (node instanceof DeleteStatement) return assembleDeleteStatement((DeleteStatement)node); else if (node instanceof UpdateStatement) return assembleUpdateStatement((UpdateStatement)node); else if (node instanceof UpdateInput) return assembleUpdateInput((UpdateInput)node); else if (node instanceof Buffer) return assembleBuffer((Buffer)node); else if (node instanceof ExpressionsHKeyScan) return assembleExpressionsHKeyScan((ExpressionsHKeyScan) node); else if (node instanceof CreateAs) return assembleCreateAsTemp((CreateAs)node); else if (node instanceof SetPlanNode) { SetPlanNode setPlan = (SetPlanNode)node; switch (setPlan.getOperationType()) { case INTERSECT: return assembleIntersect(setPlan); case EXCEPT: return assembleExcept(setPlan); case UNION: return assembleUnion(setPlan); default: throw new UnsupportedSQLException("Set operation node without type " + node, null); } } else throw new UnsupportedSQLException("Plan node " + node, null); } protected enum IntersectionMode { NONE, OUTPUT, SELECT }; protected RowStream assembleIndexScan(IndexScan index) { return assembleIndexScan(index, IntersectionMode.NONE, useSkipScan(index)); } protected RowStream assembleIndexScan(IndexScan index, IntersectionMode forIntersection, boolean useSkipScan) { if (index instanceof SingleIndexScan) return assembleSingleIndexScan((SingleIndexScan) index, forIntersection); else if (index instanceof MultiIndexIntersectScan) return assembleIndexIntersection((MultiIndexIntersectScan) index, forIntersection, useSkipScan); else throw new UnsupportedSQLException("Plan node " + index, null); } private RowStream assembleIndexIntersection(MultiIndexIntersectScan index, IntersectionMode forIntersection, boolean useSkipScan) { RowStream stream = new RowStream(); RowStream outputScan = assembleIndexScan(index.getOutputIndexScan(), (forIntersection == IntersectionMode.SELECT) ? IntersectionMode.SELECT : IntersectionMode.OUTPUT, useSkipScan); RowStream selectorScan = assembleIndexScan(index.getSelectorIndexScan(), IntersectionMode.SELECT, useSkipScan); RowType selectorRowType = selectorScan.rowType; RowType outputRowType = outputScan.rowType; int nFieldsToCompare = index.getComparisonFields(); List<TComparison> comparisons = new ArrayList<>(nFieldsToCompare); TypesRegistryService reg = rulesContext.getTypesRegistry(); // Intersect can use raw value compare if the comparisons list is null (i.e. all types the same) boolean anySet = false; for (int n = 0; n < nFieldsToCompare; ++n) { TClass left = selectorRowType.typeAt(index.getSelectorIndexScan().getPeggedCount() + n).typeClass(); TClass right = outputRowType.typeAt(index.getOutputIndexScan().getPeggedCount() + n).typeClass(); if (left != right) { anySet = true; comparisons.add(n, reg.getKeyComparable(left, right).getComparison()); } else { comparisons.add(n, null); } } stream.operator = API.intersect_Ordered( outputScan.operator, selectorScan.operator, outputRowType, selectorRowType, index.getOutputOrderingFields(), index.getSelectorOrderingFields(), index.getComparisonFieldDirections(), JoinType.INNER_JOIN, (useSkipScan) ? EnumSet.of(API.IntersectOption.OUTPUT_LEFT, API.IntersectOption.SKIP_SCAN) : EnumSet.of(API.IntersectOption.OUTPUT_LEFT, API.IntersectOption.SEQUENTIAL_SCAN), anySet ? comparisons : null, true); stream.rowType = outputScan.rowType; stream.fieldOffsets = new IndexFieldOffsets(index, stream.rowType); return stream; } protected RowStream assembleSingleIndexScan(SingleIndexScan indexScan, IntersectionMode forIntersection) { RowStream stream = new RowStream(); Index index = indexScan.getIndex(); IndexRowType indexRowType = schema.indexRowType(index); IndexScanSelector selector; if (index.isTableIndex()) { selector = IndexScanSelector.inner(index); } else { switch (index.getJoinType()) { case LEFT: selector = IndexScanSelector .leftJoinAfter(index, indexScan.getLeafMostInnerTable().getTable().getTable()); break; case RIGHT: selector = IndexScanSelector .rightJoinUntil(index, indexScan.getRootMostInnerTable().getTable().getTable()); break; default: throw new AkibanInternalException("Unknown index join type " + index); } } if (index.isSpatial()) { stream.operator = API.indexScan_Default(indexRowType, assembleSpatialIndexKeyRange(indexScan, null), API.ordering(), // TODO: what ordering? selector, rulesContext.getPipelineConfiguration().getIndexScanLookaheadQuantum()); indexRowType = indexRowType.physicalRowType(); stream.rowType = indexRowType; } else if (indexScan.getConditionRange() == null) { stream.operator = API.indexScan_Default(indexRowType, assembleIndexKeyRange(indexScan, null), assembleIndexOrdering(indexScan, indexRowType), selector, rulesContext.getPipelineConfiguration().getIndexScanLookaheadQuantum()); stream.rowType = indexRowType; } else { ColumnRanges range = indexScan.getConditionRange(); // Non-single-point ranges are ordered by the ranges // themselves as part of merging segments, so that index // column is an ordering column. // Single-point ranges have only one value for the range column, // so they can order by the following columns if we're // willing to do the more expensive ordered union. // Determine whether anything is taking advantage of this: // * Index is being intersected. // * Index is effective for query ordering. // ** See also special case in AggregateSplitter.directIndexMinMax(). boolean unionOrdered = false, unionOrderedAll = false; if (range.isAllSingle()) { if (forIntersection != IntersectionMode.NONE) { unionOrdered = true; if (forIntersection == IntersectionMode.OUTPUT) { unionOrderedAll = true; } } else if (indexScan.getOrderEffectiveness() != IndexScan.OrderEffectiveness.NONE) { unionOrderedAll = unionOrdered = true; } } for (RangeSegment rangeSegment : range.getSegments()) { Operator scan = API.indexScan_Default(indexRowType, assembleIndexKeyRange(indexScan, null, rangeSegment), assembleIndexOrdering(indexScan, indexRowType), selector, rulesContext.getPipelineConfiguration().getIndexScanLookaheadQuantum()); if (stream.operator == null) { stream.operator = scan; stream.rowType = indexRowType; } else if (unionOrdered) { int nequals = indexScan.getNEquality(); List<OrderByExpression> ordering = indexScan.getOrdering(); int nordering = ordering.size() - nequals; boolean[] ascending = new boolean[nordering]; for (int i = 0; i < nordering; i++) { ascending[i] = ordering.get(nequals + i).isAscending(); } stream.operator = API.union_Ordered(stream.operator, scan, (IndexRowType)stream.rowType, indexRowType, nordering, nordering, ascending, unionOrderedAll); } else { stream.operator = API.unionAll_Default(stream.operator, stream.rowType, scan, indexRowType, rulesContext.getPipelineConfiguration().isUnionAllOpenBoth()); stream.rowType = stream.operator.rowType(); } } if (stream.operator == null) { stream.operator = API.valuesScan_Default(Collections.<BindableRow>emptyList(), indexRowType); stream.rowType = indexRowType; } } stream.fieldOffsets = new IndexFieldOffsets(indexScan, indexRowType); if (explainContext != null) explainSingleIndexScan(stream.operator, indexScan, index); return stream; } protected void explainSingleIndexScan(Operator operator, SingleIndexScan indexScan, Index index) { Attributes atts = new Attributes(); atts.put(Label.ORDER_EFFECTIVENESS, PrimitiveExplainer.getInstance(indexScan.getOrderEffectiveness().name())); atts.put(Label.USED_COLUMNS, PrimitiveExplainer.getInstance(indexScan.usesAllColumns() ? indexScan.getColumns().size() : indexScan.getNKeyColumns())); explainCostEstimate(atts, indexScan.getScanCostEstimate()); explainContext.putExtraInfo(operator, new CompoundExplainer(Type.EXTRA_INFO, atts)); } protected void explainCostEstimate(Attributes atts, CostEstimate costEstimate) { if (costEstimate != null) atts.put(Label.COST, PrimitiveExplainer.getInstance(costEstimate.toString())); } /** * If there are this many or more scans feeding into a tree * of intersection / union, then skip scan is enabled for it. * (3 scans means 2 intersections or intersection with a two-value union.) */ public static final int SKIP_SCAN_MIN_COUNT_DEFAULT = 3; protected boolean useSkipScan(IndexScan index) { if (!(index instanceof MultiIndexIntersectScan)) return false; int count = countScans(index); int minCount; String prop = rulesContext.getProperty("skipScanMinCount"); if (prop != null) minCount = Integer.valueOf(prop); else minCount = SKIP_SCAN_MIN_COUNT_DEFAULT; return (count >= minCount); } private int countScans(IndexScan index) { if (index instanceof SingleIndexScan) { SingleIndexScan sindex = (SingleIndexScan)index; if (sindex.getConditionRange() == null) return 1; else return sindex.getConditionRange().getSegments().size(); } else if (index instanceof MultiIndexIntersectScan) { MultiIndexIntersectScan mindex = (MultiIndexIntersectScan)index; return countScans(mindex.getOutputIndexScan()) + countScans(mindex.getSelectorIndexScan()); } else return 0; } protected RowStream assembleGroupScan(GroupScan groupScan) { RowStream stream = new RowStream(); Group group = groupScan.getGroup().getGroup(); stream.operator = API.groupScan_Default(group); stream.unknownTypesPresent = true; return stream; } protected RowStream assembleExpressionsSource(ExpressionsSource expressionsSource) { RowStream stream = new RowStream(); stream.rowType = valuesRowType(expressionsSource); List<BindableRow> bindableRows = new ArrayList<>(); for (List<ExpressionNode> exprs : expressionsSource.getExpressions()) { List<TPreparedExpression> tExprs = assembleExpressions(exprs, stream.fieldOffsets); bindableRows.add(BindableRow.of(stream.rowType, tExprs, planContext.getQueryContext())); } stream.operator = API.valuesScan_Default(bindableRows, stream.rowType); stream.fieldOffsets = new ColumnSourceFieldOffsets(expressionsSource, stream.rowType); if (expressionsSource.getDistinctState() == DistinctState.NEED_DISTINCT) { // Add Sort (usually _InsertionLimited) and Distinct. assembleSort(stream, stream.rowType.nFields(), expressionsSource, API.SortOption.SUPPRESS_DUPLICATES); } return stream; } protected RowStream assembleExpressionsHKeyScan(ExpressionsHKeyScan node) { RowStream stream = new RowStream(); stream.rowType = schema.newHKeyRowType(node.getHKey()); List<TPreparedExpression> keys = assembleExpressions(node.getKeys(), stream.fieldOffsets); stream.operator = API.hKeyRow_Default(stream.rowType, keys); return stream; } protected RowStream assembleSubquerySource(SubquerySource subquerySource) { PlanNode subquery = subquerySource.getSubquery().getQuery(); if (subquery instanceof ResultSet) subquery = ((ResultSet)subquery).getInput(); RowStream stream = assembleStream(subquery); stream.fieldOffsets = new ColumnSourceFieldOffsets(subquerySource, stream.rowType); return stream; } protected RowStream assembleNullSource(NullSource node) { return assembleExpressionsSource(new ExpressionsSource(Collections.<List<ExpressionNode>>emptyList())); } protected RowStream assembleSelect(Select select) { RowStream stream = assembleStream(select.getInput()); ConditionDependencyAnalyzer dependencies = null; for (ConditionExpression condition : select.getConditions()) { RowType rowType = stream.rowType; ColumnExpressionToIndex fieldOffsets = stream.fieldOffsets; if (rowType == null) { // Pre-flattening case: get the single table this // condition must have come from and use its row-type. // TODO: Would it be better if earlier rule saved this? if (dependencies == null) dependencies = new ConditionDependencyAnalyzer(select); TableSource table = (TableSource)dependencies.analyze(condition); rowType = tableRowType(table); fieldOffsets = new ColumnSourceFieldOffsets(table, rowType); } stream.operator = API.select_HKeyOrdered(stream.operator, rowType, assembleExpression(condition, fieldOffsets)); } return stream; } protected RowStream assembleFlatten(Flatten flatten) { RowStream stream = assembleStream(flatten.getInput()); List<TableNode> tableNodes = flatten.getTableNodes(); TableNode tableNode = tableNodes.get(0); RowType tableRowType = tableRowType(tableNode); stream.rowType = tableRowType; int ntables = tableNodes.size(); if (ntables == 1) { TableSource tableSource = flatten.getTableSources().get(0); if (tableSource != null) stream.fieldOffsets = new ColumnSourceFieldOffsets(tableSource, tableRowType); } else { Flattened flattened = new Flattened(); flattened.addTable(tableRowType, flatten.getTableSources().get(0)); for (int i = 1; i < ntables; i++) { tableNode = tableNodes.get(i); tableRowType = tableRowType(tableNode); flattened.addTable(tableRowType, flatten.getTableSources().get(i)); API.JoinType flattenType = null; switch (flatten.getJoinTypes().get(i-1)) { case INNER: flattenType = API.JoinType.INNER_JOIN; break; case LEFT: flattenType = API.JoinType.LEFT_JOIN; break; case RIGHT: flattenType = API.JoinType.RIGHT_JOIN; break; case FULL_OUTER: flattenType = API.JoinType.FULL_JOIN; break; } stream.operator = API.flatten_HKeyOrdered(stream.operator, stream.rowType, tableRowType, flattenType); stream.rowType = stream.operator.rowType(); } flattened.setRowType(stream.rowType); stream.fieldOffsets = flattened; } if (stream.unknownTypesPresent) { stream.operator = API.filter_Default(stream.operator, Collections.singletonList(stream.rowType)); stream.unknownTypesPresent = false; } return stream; } protected RowStream assembleAncestorLookup(AncestorLookup ancestorLookup) { RowStream stream; Group group = ancestorLookup.getDescendant().getGroup(); List<TableRowType> outputRowTypes = new ArrayList<>(ancestorLookup.getAncestors().size()); for (TableNode table : ancestorLookup.getAncestors()) { outputRowTypes.add(tableRowType(table)); } PlanNode input = ancestorLookup.getInput(); if (input instanceof GroupLoopScan) { stream = new RowStream(); ColumnExpressionToIndex boundRow = lookupNestedBoundRow(((GroupLoopScan)ancestorLookup.getInput())); stream.operator = API.ancestorLookup_Nested(group, boundRow.getRowType(), outputRowTypes, getBindingPosition(boundRow), rulesContext.getPipelineConfiguration().getGroupLookupLookaheadQuantum()); } else { BranchLookup branchLookup = null; if (input instanceof BranchLookup) { branchLookup = (BranchLookup)input; if ((branchLookup.getInput() == null) || (branchLookup.getSource().getGroup() != group)) { branchLookup = null; } } if (branchLookup != null) { for (TableSource table : branchLookup.getTables()) { outputRowTypes.add(tableRowType(table)); } stream = assembleStream(branchLookup.getInput()); stream.unknownTypesPresent = true; } else stream = assembleStream(input); RowType inputRowType = stream.rowType; // The index row type. API.InputPreservationOption flag = API.InputPreservationOption.DISCARD_INPUT; if (!isIndexRowType(inputRowType)) { // Getting from branch lookup. inputRowType = tableRowType(ancestorLookup.getDescendant()); flag = API.InputPreservationOption.KEEP_INPUT; } stream.operator = API.groupLookup_Default(stream.operator, group, inputRowType, outputRowTypes, flag, rulesContext.getPipelineConfiguration().getGroupLookupLookaheadQuantum()); } stream.rowType = null; stream.fieldOffsets = null; return stream; } protected RowStream assembleBranchLookup(BranchLookup branchLookup) { RowStream stream; Group group = branchLookup.getSource().getGroup(); List<TableRowType> outputRowTypes = new ArrayList<>(branchLookup.getTables().size()); if (false) // TODO: Any way to check that this matched? outputRowTypes.add(tableRowType(branchLookup.getBranch())); for (TableSource table : branchLookup.getTables()) { outputRowTypes.add(tableRowType(table)); } if (branchLookup.getInput() == null) { // Simple version for Product_Nested. stream = new RowStream(); API.InputPreservationOption flag = API.InputPreservationOption.KEEP_INPUT; ColumnExpressionToIndex boundRow = boundRows.peek(); stream.operator = API.branchLookup_Nested(group, boundRow.getRowType(), tableRowType(branchLookup.getSource()), tableRowType(branchLookup.getAncestor()), outputRowTypes, flag, getBindingPosition(boundRow), rulesContext.getPipelineConfiguration().getGroupLookupLookaheadQuantum()); } else if (branchLookup.getInput() instanceof GroupLoopScan) { // Fuller version for group join across subquery boundary. stream = new RowStream(); API.InputPreservationOption flag = API.InputPreservationOption.DISCARD_INPUT; ColumnExpressionToIndex boundRow = lookupNestedBoundRow(((GroupLoopScan)branchLookup.getInput())); stream.operator = API.branchLookup_Nested(group, boundRow.getRowType(), boundRow.getRowType(), tableRowType(branchLookup.getAncestor()), outputRowTypes, flag, getBindingPosition(boundRow), rulesContext.getPipelineConfiguration().getGroupLookupLookaheadQuantum()); } else { // Ordinary inline version. stream = assembleStream(branchLookup.getInput()); RowType inputRowType = stream.rowType; // The index row type. API.InputPreservationOption flag = API.InputPreservationOption.DISCARD_INPUT; if (!isIndexRowType(inputRowType)) { // Getting from ancestor lookup. inputRowType = tableRowType(branchLookup.getSource()); flag = API.InputPreservationOption.KEEP_INPUT; } stream.operator = API.groupLookup_Default(stream.operator, group, inputRowType, outputRowTypes, flag, rulesContext.getPipelineConfiguration().getGroupLookupLookaheadQuantum()); } stream.rowType = null; stream.unknownTypesPresent = true; stream.fieldOffsets = null; return stream; } protected static boolean isIndexRowType(RowType rowType) { return ((rowType instanceof IndexRowType) || (rowType instanceof HKeyRowType)); } protected RowStream assembleUnion(SetPlanNode union) { PlanNode left = union.getLeft(); if (left instanceof ResultSet) left = ((ResultSet) left).getInput(); PlanNode right = union.getRight(); if (right instanceof ResultSet) right = ((ResultSet) right).getInput(); RowStream leftStream = assembleStream(left); RowStream rightStream = assembleStream(right); if (union.isAll()) { leftStream.operator = API.unionAll_Default(leftStream.operator, leftStream.rowType, rightStream.operator, rightStream.rowType, rulesContext.getPipelineConfiguration().isUnionAllOpenBoth()); } else { //Union ordered assumes sorted order, so sort the input streams. //TODO: Is there a way to determine if this is a requirement? leftStream.operator = API.sort_General(leftStream.operator, leftStream.rowType, assembleSetOrdering(leftStream.rowType), API.SortOption.SUPPRESS_DUPLICATES); leftStream.rowType = leftStream.operator.rowType(); rightStream.operator = API.sort_General(rightStream.operator, rightStream.rowType, assembleSetOrdering(rightStream.rowType), API.SortOption.SUPPRESS_DUPLICATES); rightStream.rowType = rightStream.operator.rowType(); boolean[] ascending = new boolean[rightStream.rowType.nFields()]; Arrays.fill(ascending, Boolean.TRUE); RowType leftRowType = leftStream.rowType; RowType rightRowType = rightStream.rowType; int leftOrderingFields = leftRowType.nFields(); int rightOrderingFields = rightRowType.nFields(); leftStream.operator = API.union_Ordered(leftStream.operator, rightStream.operator, leftRowType, rightRowType, leftOrderingFields, rightOrderingFields, ascending, false); } leftStream.rowType = leftStream.operator.rowType(); return leftStream; } protected RowStream assembleIntersect(SetPlanNode intersect) { PlanNode left = intersect.getLeft(); if (left instanceof ResultSet) left = ((ResultSet)left).getInput(); PlanNode right = intersect.getRight(); if (right instanceof ResultSet) right = ((ResultSet)right).getInput(); RowStream leftStream = assembleStream (left); RowStream rightStream = assembleStream (right); boolean[] ascending = new boolean[rightStream.rowType.nFields()]; Arrays.fill(ascending, Boolean.TRUE); RowType leftRowType = leftStream.rowType; RowType rightRowType = rightStream.rowType; int leftOrderingFields = leftRowType.nFields(); int rightOrderingFields = rightRowType.nFields(); if (intersect.isAll()) { leftStream.operator = API.sort_General(leftStream.operator, leftStream.rowType, assembleSetOrdering(leftStream.rowType), API.SortOption.PRESERVE_DUPLICATES); leftStream.rowType = leftStream.operator.rowType(); rightStream.operator = API.sort_General(rightStream.operator, rightStream.rowType, assembleSetOrdering(rightStream.rowType), API.SortOption.PRESERVE_DUPLICATES); rightStream.rowType = rightStream.operator.rowType(); leftStream.operator = API.intersect_Ordered(leftStream.operator, rightStream.operator, leftRowType, rightRowType, leftOrderingFields, rightOrderingFields, ascending, JoinType.INNER_JOIN, EnumSet.of(IntersectOption.OUTPUT_LEFT, IntersectOption.SEQUENTIAL_SCAN), null); } else { leftStream.operator = API.sort_General(leftStream.operator, leftStream.rowType, assembleSetOrdering(leftStream.rowType), API.SortOption.SUPPRESS_DUPLICATES); leftStream.rowType = leftStream.operator.rowType(); rightStream.operator = API.sort_General(rightStream.operator, rightStream.rowType, assembleSetOrdering(rightStream.rowType), API.SortOption.SUPPRESS_DUPLICATES); rightStream.rowType = rightStream.operator.rowType(); leftStream.operator = API.intersect_Ordered(leftStream.operator, rightStream.operator, leftRowType, rightRowType, leftOrderingFields, rightOrderingFields, ascending, JoinType.INNER_JOIN, EnumSet.of(IntersectOption.OUTPUT_LEFT, IntersectOption.SEQUENTIAL_SCAN), null); }//TODO add instance if input rows are already sorted leftStream.rowType = leftStream.operator.rowType(); return leftStream; } protected RowStream assembleExcept(SetPlanNode except) { PlanNode left = except.getLeft(); if (left instanceof ResultSet) left = ((ResultSet)left).getInput(); PlanNode right = except.getRight(); if (right instanceof ResultSet) right = ((ResultSet)right).getInput(); RowStream leftStream = assembleStream (left); RowStream rightStream = assembleStream (right); boolean[] ascending = new boolean[rightStream.rowType.nFields()]; Arrays.fill(ascending, Boolean.TRUE); RowType leftRowType = leftStream.rowType; RowType rightRowType = rightStream.rowType; int leftOrderingFields = leftRowType.nFields(); int rightOrderingFields = rightRowType.nFields(); if (except.isAll()) { leftStream.operator = API.sort_General(leftStream.operator, leftStream.rowType, assembleSetOrdering(leftStream.rowType), API.SortOption.PRESERVE_DUPLICATES); leftStream.rowType = leftStream.operator.rowType(); rightStream.operator = API.sort_General(rightStream.operator, rightStream.rowType, assembleSetOrdering(rightStream.rowType), API.SortOption.PRESERVE_DUPLICATES); rightStream.rowType = rightStream.operator.rowType(); leftStream.operator = API.except_Ordered(leftStream.operator, rightStream.operator, leftRowType, rightRowType, leftOrderingFields, rightOrderingFields, ascending, false); } else { leftStream.operator = API.sort_General(leftStream.operator, leftStream.rowType, assembleSetOrdering(leftStream.rowType), API.SortOption.SUPPRESS_DUPLICATES); leftStream.rowType = leftStream.operator.rowType(); rightStream.operator = API.sort_General(rightStream.operator, rightStream.rowType, assembleSetOrdering(rightStream.rowType), API.SortOption.SUPPRESS_DUPLICATES); rightStream.rowType = rightStream.operator.rowType(); leftStream.operator = API.except_Ordered(leftStream.operator, rightStream.operator, leftRowType, rightRowType, leftOrderingFields, rightOrderingFields, ascending, false); }//TODO add instance if input rows are already sorted leftStream.rowType = leftStream.operator.rowType(); return leftStream; } protected API.Ordering assembleSetOrdering(RowType rowType) { API.Ordering ordering = createOrdering(); for (int i = 0; i < rowType.nFields(); i++) { TPreparedExpression tExpr = field(rowType, i); if(rowType.fieldHasColumn(i)) ordering.append(tExpr, true, rowType.fieldColumn(i).getCollator()); else ordering.append(tExpr, true, AkCollatorFactory.UCS_BINARY_COLLATOR ); } return ordering; } protected RowStream assembleMapJoin(MapJoin mapJoin) { PlanNode outer = mapJoin.getOuter(); RowStream ostream = assembleStream(outer); int pos = pushBoundRow(ostream.fieldOffsets); nestedBindingsDepth++; RowStream stream = assembleStream(mapJoin.getInner()); stream.operator = API.map_NestedLoops(ostream.operator, stream.operator, pos, rulesContext.getPipelineConfiguration().isMapEnabled(), nestedBindingsDepth); nestedBindingsDepth--; popBoundRow(); return stream; } protected RowStream assembleProduct(Product product) { TableRowType ancestorRowType = null; if (product.getAncestor() != null) ancestorRowType = tableRowType(product.getAncestor()); RowStream pstream = new RowStream(); Flattened flattened = new Flattened(); pstream.fieldOffsets = flattened; int nbound = 0; int bindingPosition = -1; for (PlanNode subplan : product.getSubplans()) { if (pstream.operator != null) { bindingPosition = pushBoundRow(flattened); if (nbound++ == 0) { // Only one deeper for all, since nest on the outer side. nestedBindingsDepth++; } } RowStream stream = assembleStream(subplan); if (pstream.operator == null) { pstream.operator = stream.operator; pstream.rowType = stream.rowType; } else { stream.operator = API.product_Nested(stream.operator, pstream.rowType, ancestorRowType, stream.rowType, bindingPosition); stream.rowType = stream.operator.rowType(); pstream.operator = API.map_NestedLoops(pstream.operator, stream.operator, bindingPosition, rulesContext.getPipelineConfiguration().isMapEnabled(), nestedBindingsDepth); pstream.rowType = stream.rowType; } if (stream.fieldOffsets instanceof ColumnSourceFieldOffsets) { TableSource table = ((ColumnSourceFieldOffsets) stream.fieldOffsets).getTable(); flattened.addTable(tableRowType(table), table); } else { flattened.product((Flattened)stream.fieldOffsets); } flattened.setRowType(pstream.rowType); } if (nbound > 0) { nestedBindingsDepth--; } while (nbound > 0) { popBoundRow(); nbound--; } return pstream; } protected RowStream assembleAggregateSource(AggregateSource aggregateSource) { AggregateSource.Implementation impl = aggregateSource.getImplementation(); if (impl == null) impl = AggregateSource.Implementation.SORT; int nkeys = aggregateSource.getNGroupBy(); RowStream stream; switch (impl) { case COUNT_STAR: case COUNT_TABLE_STATUS: { assert !aggregateSource.isProjectSplitOff(); assert ((nkeys == 0) && (aggregateSource.getNAggregates() == 1)); if (impl == AggregateSource.Implementation.COUNT_STAR) { stream = assembleStream(aggregateSource.getInput()); // TODO: Could be removed, since aggregate_Partial works as well. stream.operator = API.count_Default(stream.operator, stream.rowType); } else { stream = new RowStream(); stream.operator = API.count_TableStatus(tableRowType(aggregateSource.getTable())); } stream.rowType = stream.operator.rowType(); stream.fieldOffsets = new ColumnSourceFieldOffsets(aggregateSource, stream.rowType); return stream; } } assert aggregateSource.isProjectSplitOff(); stream = assembleStream(aggregateSource.getInput()); switch (impl) { case PRESORTED: case UNGROUPED: break; case FIRST_FROM_INDEX: { assert ((nkeys == 0) && (aggregateSource.getNAggregates() == 1)); stream.operator = API.limit_Default(stream.operator, 1); stream = assembleNullIfEmpty(stream); stream.fieldOffsets = new ColumnSourceFieldOffsets(aggregateSource, stream.rowType); return stream; } default: // TODO: Could pre-aggregate now in PREAGGREGATE_RESORT case. assembleSort(stream, nkeys, aggregateSource.getInput(), API.SortOption.PRESERVE_DUPLICATES); break; } stream.operator = assembleAggregates(stream.operator, stream.rowType, nkeys, aggregateSource); stream.rowType = stream.operator.rowType(); stream.fieldOffsets = new ColumnSourceFieldOffsets(aggregateSource, stream.rowType); return stream; } protected RowStream assembleDistinct(Distinct distinct) { Distinct.Implementation impl = distinct.getImplementation(); if (impl == Distinct.Implementation.EXPLICIT_SORT) { PlanNode input = distinct.getInput(); if (input instanceof Sort) { // Explicit Sort is still there; combine. return assembleSort((Sort)input, distinct.getOutput(), API.SortOption.SUPPRESS_DUPLICATES); } // Sort removed but Distinct remains. impl = Distinct.Implementation.PRESORTED; } RowStream stream = assembleStream(distinct.getInput()); if (impl == null) impl = Distinct.Implementation.SORT; switch (impl) { case PRESORTED: List<AkCollator> collators = findCollators(distinct.getInput()); if (collators != null) { stream.operator = API.distinct_Partial(stream.operator, stream.rowType, collators); } else { throw new UnsupportedOperationException("Cannot find collators for Distinct_Partial from " + distinct.getInput()); } break; default: assembleSort(stream, stream.rowType.nFields(), distinct.getInput(), API.SortOption.SUPPRESS_DUPLICATES); break; } return stream; } // Hack to handle some easy and common cases until types3. private List<AkCollator> findCollators(PlanNode node) { if ((node instanceof Sort) || (node instanceof UsingLoaderBase)) { return findCollators(((BasePlanWithInput)node).getInput()); } else if (node instanceof MapJoin) { return findCollators(((MapJoin)node).getInner()); } else if (node instanceof Project) { List<AkCollator> collators = new ArrayList<>(); Project project = (Project) node; for (ExpressionNode expressionNode : project.getFields()) { collators.add(expressionNode.getCollator()); } return collators; } else if (node instanceof IndexScan) { List<AkCollator> collators = new ArrayList<>(); IndexScan indexScan = (IndexScan) node; for (IndexColumn indexColumn : indexScan.getIndexColumns()) { collators.add(indexColumn.getColumn().getCollator()); } return collators; } else { return null; } } protected RowStream assembleSort(Sort sort) { return assembleSort(sort, sort.getOutput(), API.SortOption.PRESERVE_DUPLICATES); } protected RowStream assembleSort(Sort sort, PlanNode output, API.SortOption sortOption) { RowStream stream = assembleStream(sort.getInput()); API.Ordering ordering = createOrdering(); for (OrderByExpression orderBy : sort.getOrderBy()) { TPreparedExpression tExpr = assembleExpression(orderBy.getExpression(), stream.fieldOffsets); ordering.append(tExpr, orderBy.isAscending(), orderBy.getCollator()); } assembleSort(stream, ordering, sort.getInput(), output, sortOption); return stream; } protected void assembleSort(RowStream stream, API.Ordering ordering, PlanNode input, PlanNode output, API.SortOption sortOption) { int maxrows = -1; if (output instanceof Project) { output = output.getOutput(); } if (output instanceof Limit) { Limit limit = (Limit)output; if (!limit.isOffsetParameter() && !limit.isLimitParameter() && (limit.getLimit() >= 0)) { maxrows = limit.getOffset() + limit.getLimit(); } } else if (input instanceof ExpressionsSource) { ExpressionsSource expressionsSource = (ExpressionsSource)input; maxrows = expressionsSource.getExpressions().size(); } if ((maxrows >= 0) && (maxrows <= INSERTION_SORT_MAX_LIMIT)) stream.operator = API.sort_InsertionLimited(stream.operator, stream.rowType, ordering, sortOption, maxrows); else stream.operator = API.sort_General(stream.operator, stream.rowType, ordering, sortOption); } protected void assembleSort(RowStream stream, int nkeys, PlanNode input, API.SortOption sortOption) { List<AkCollator> collators = findCollators(input); API.Ordering ordering = createOrdering(); for (int i = 0; i < nkeys; i++) { TPreparedExpression tExpr = field(stream.rowType, i); ordering.append(tExpr, true, (collators == null) ? null : collators.get(i)); } assembleSort(stream, ordering, input, null, sortOption); } protected RowStream assembleBuffer(Buffer buffer) { RowStream stream = assembleStream(buffer.getInput()); stream.operator = API.buffer_Default(stream.operator, stream.rowType); return stream; } protected RowStream assembleLimit(Limit limit) { RowStream stream = assembleStream(limit.getInput()); int nlimit = limit.getLimit(); if ((nlimit < 0) && !limit.isLimitParameter()) nlimit = Integer.MAX_VALUE; // Slight disagreement in saying unlimited. stream.operator = API.limit_Default(stream.operator, limit.getOffset(), limit.isOffsetParameter(), nlimit, limit.isLimitParameter()); return stream; } protected RowStream assembleNullIfEmpty(NullIfEmpty nullIfEmpty) { RowStream stream = assembleStream(nullIfEmpty.getInput()); return assembleNullIfEmpty(stream); } protected RowStream assembleNullIfEmpty(RowStream stream) { stream.operator = ifEmptyNulls(stream.operator, stream.rowType, API.InputPreservationOption.KEEP_INPUT); return stream; } protected RowStream assembleOnlyIfEmpty(OnlyIfEmpty onlyIfEmpty) { RowStream stream = assembleStream(onlyIfEmpty.getInput()); stream.operator = API.limit_Default(stream.operator, 0, false, 1, false); // Nulls here have no semantic meaning, but they're easier than trying to // figure out an interesting non-null value for each // type in the row. All that really matters is that the // row is there. stream.operator = ifEmptyNulls(stream.operator, stream.rowType, API.InputPreservationOption.DISCARD_INPUT); return stream; } protected RowStream assembleUsingBloomFilter(UsingBloomFilter usingBloomFilter) { BloomFilter bloomFilter = usingBloomFilter.getBloomFilter(); int pos = assignBindingPosition(bloomFilter); RowStream lstream = assembleStream(usingBloomFilter.getLoader()); RowStream stream = assembleStream(usingBloomFilter.getInput()); List<AkCollator> collators = findCollators(usingBloomFilter.getLoader()); stream.operator = API.using_BloomFilter(lstream.operator, lstream.rowType, bloomFilter.getEstimatedSize(), pos, stream.operator, collators); return stream; } protected RowStream assembleBloomFilterFilter(BloomFilterFilter bloomFilterFilter) { BloomFilter bloomFilter = bloomFilterFilter.getBloomFilter(); int pos = getBindingPosition(bloomFilter); RowStream stream = assembleStream(bloomFilterFilter.getInput()); bindingPositions.put(stream.fieldOffsets, pos); // Shares the slot. boundRows.push(stream.fieldOffsets); nestedBindingsDepth++; RowStream cstream = assembleStream(bloomFilterFilter.getCheck()); boundRows.pop(); List<TPreparedExpression> tFields = assembleExpressions(bloomFilterFilter.getLookupExpressions(), stream.fieldOffsets); List<AkCollator> collators = findCollators(bloomFilterFilter.getInput()); stream.operator = API.select_BloomFilter(stream.operator, cstream.operator, tFields, collators, pos, rulesContext.getPipelineConfiguration().isSelectBloomFilterEnabled(), nestedBindingsDepth); nestedBindingsDepth--; return stream; } protected RowStream assembleUsingHashTable( UsingHashTable usingHashTable) { HashTable hashTable = usingHashTable.getHashTable(); int pos = assignBindingPosition(hashTable); RowStream lstream = assembleStream(usingHashTable.getLoader()); hashTableLoaders.put(hashTable, lstream); RowStream stream = assembleStream(usingHashTable.getInput()); List<ExpressionNode> expressionNodes = usingHashTable.getLookupExpressions(); List<TPreparedExpression> tFields = assembleExpressions(expressionNodes,lstream.fieldOffsets); List<TComparison> tComparisons = new ArrayList<>(); boolean allNull = true; for (TKeyComparable comparable : usingHashTable.getTKeyComparables()) { if (comparable == null) { tComparisons.add(null); } else { tComparisons.add(comparable.getComparison()); allNull = false; } } if (allNull) tComparisons = null; List<AkCollator> collators = usingHashTable.getCollators(); allNull = true; for (AkCollator collator : collators) { if (collator != null) { allNull = false; break; } } if (allNull) collators = null; stream.operator = API.using_HashTable(lstream.operator, lstream.rowType, tFields, pos, stream.operator, tComparisons, collators); return stream; } protected RowStream assembleHashTableLookup(HashTableLookup hashTableLookup) { HashTable hashTable = hashTableLookup.getHashTable(); int tablePos = getBindingPosition(hashTable); RowStream lstream = hashTableLoaders.get(hashTable); List<ExpressionNode> expressionNodes = hashTableLookup.getLookupExpressions(); List<TPreparedExpression> tFields = assembleExpressions(hashTableLookup.getLookupExpressions(), lstream.fieldOffsets); RowStream stream = new RowStream(); stream.rowType = lstream.rowType; stream.fieldOffsets = lstream.fieldOffsets; stream.operator = API.hashTableLookup_Default( stream.rowType, tFields, tablePos); return stream; } protected RowStream assembleProject(Project project) { RowStream stream = assembleStream(project.getInput()); List<? extends TPreparedExpression> pExpressions; pExpressions = assembleExpressions(project.getFields(), stream.fieldOffsets); stream.operator = API.project_Default(stream.operator, stream.rowType, pExpressions); stream.rowType = stream.operator.rowType(); stream.fieldOffsets = new ColumnSourceFieldOffsets(project, stream.rowType); return stream; } // Get a list of result columns based on ResultSet expression names. protected List<PhysicalResultColumn> getResultColumns(List<ResultField> fields) { List<PhysicalResultColumn> columns = new ArrayList<>(fields.size()); for (ResultField field : fields) { columns.add(rulesContext.getResultColumn(field)); } return columns; } // Get a list of result columns for unnamed columns. // This would correspond to top-level VALUES, which the parser // does not currently support. protected List<PhysicalResultColumn> getResultColumns(int ncols) { List<PhysicalResultColumn> columns = new ArrayList<>(ncols); for (int i = 0; i < ncols; i++) { columns.add(rulesContext.getResultColumn(new ResultField("column" + (i+1)))); } return columns; } // Generate key range bounds. protected IndexKeyRange assembleIndexKeyRange(SingleIndexScan index, ColumnExpressionToIndex fieldOffsets) { return assembleIndexKeyRange( index, fieldOffsets, index.getLowComparand(), index.isLowInclusive(), index.getHighComparand(), index.isHighInclusive() ); } protected IndexKeyRange assembleIndexKeyRange(SingleIndexScan index, ColumnExpressionToIndex fieldOffsets, RangeSegment segment) { return assembleIndexKeyRange( index, fieldOffsets, segment.getStart().getValueExpression(), segment.getStart().isInclusive(), segment.getEnd().getValueExpression(), segment.getEnd().isInclusive() ); } private IndexKeyRange assembleIndexKeyRange(SingleIndexScan index, ColumnExpressionToIndex fieldOffsets, ExpressionNode lowComparand, boolean lowInclusive, ExpressionNode highComparand, boolean highInclusive) { List<ExpressionNode> equalityComparands = index.getEqualityComparands(); IndexRowType indexRowType = getIndexRowType(index); if ((equalityComparands == null) && (lowComparand == null) && (highComparand == null)) return IndexKeyRange.unbounded(indexRowType); int nkeys = 0; if (equalityComparands != null) nkeys = equalityComparands.size(); if ((lowComparand != null) || (highComparand != null)) nkeys++; TPreparedExpression[] pkeys = new TPreparedExpression[nkeys]; int kidx = 0; if (equalityComparands != null) { for (ExpressionNode comp : equalityComparands) { if (!(comp instanceof IsNullIndexKey)) { // Java null means IS NULL; Null expression wouldn't match. assembleExpressionInto(comp, fieldOffsets, pkeys, kidx); } kidx++; } } if ((lowComparand == null) && (highComparand == null)) { IndexBound eq = getIndexBound(index.getIndex(), pkeys, kidx); return IndexKeyRange.bounded(indexRowType, eq, true, eq, true); } else { TPreparedExpression[] lowPKeys = null, highPKeys = null; boolean lowInc = false, highInc = false; int lidx = kidx, hidx = kidx; if ((lidx > 0) || (lowComparand != null)) { lowPKeys = pkeys; if ((hidx > 0) || (highComparand != null)) { highPKeys = new TPreparedExpression[nkeys]; System.arraycopy(pkeys, 0, highPKeys, 0, nkeys); } } else if (highComparand != null) { highPKeys = pkeys; } if (lowComparand != null) { assembleExpressionInto(lowComparand, fieldOffsets, lowPKeys, lidx); lidx++; lowInc = lowInclusive; } if (highComparand != null) { assembleExpressionInto(highComparand, fieldOffsets, highPKeys, hidx); hidx++; highInc = highInclusive; } int bounded = lidx > hidx ? lidx : hidx; IndexBound lo = getIndexBound(index.getIndex(), lowPKeys, bounded); IndexBound hi = getIndexBound(index.getIndex(), highPKeys, bounded); assert lo != null || hi != null; if (lo == null) { lo = getNullIndexBound(index.getIndex(), hidx); } if (hi == null) { hi = getNullIndexBound(index.getIndex(), lidx); } return IndexKeyRange.bounded(indexRowType, lo, lowInc, hi, highInc); } } protected API.Ordering assembleIndexOrdering(IndexScan index, IndexRowType indexRowType) { API.Ordering ordering = createOrdering(); List<OrderByExpression> indexOrdering = index.getOrdering(); for (int i = 0; i < indexOrdering.size(); i++) { TPreparedExpression tExpr = field(indexRowType, i); ordering.append(tExpr, indexOrdering.get(i).isAscending(), index.getIndexColumns().get(i).getColumn().getCollator()); } assert ordering.allAscending() || ordering.allDescending() : "No index scan mixed ordering"; return ordering; } protected TableRowType tableRowType(TableSource table) { return tableRowType(table.getTable()); } protected TableRowType tableRowType(TableNode table) { Table aisTable = table.getTable(); affectedTables.add(aisTable); return schema.tableRowType(aisTable); } protected IndexRowType getIndexRowType(SingleIndexScan index) { Index aisIndex = index.getIndex(); AkibanInformationSchema ais = schema.ais(); for (int i : aisIndex.getAllTableIDs()) { affectedTables.add(ais.getTable(i)); } return schema.indexRowType(aisIndex); } /** Return an index bound for the given index and expressions. * @param index the index in use * @param keys {@link Expression}s for index lookup key * @param nBoundKeys number of keys actually in use */ protected IndexBound getIndexBound(Index index, TPreparedExpression[] pKeys, int nBoundKeys) { if (pKeys == null) return null; TPreparedExpression[] boundPKeys; int nkeys = pKeys.length; if (nBoundKeys < nkeys) { Object[] source, dest; boundPKeys = new TPreparedExpression[nBoundKeys]; source = pKeys; dest = boundPKeys; System.arraycopy(source, 0, dest, 0, nBoundKeys); } else { boundPKeys = pKeys; } fillNulls(index, pKeys); return new IndexBound(getIndexExpressionRow(index, boundPKeys), getIndexColumnSelector(index, nBoundKeys)); } /** Return an index bound for the given index containing all nulls. * @param index the index in use * @param nkeys number of keys actually in use */ protected IndexBound getNullIndexBound(Index index, int nkeys) { TPreparedExpression[] pKeys = createNulls(index, nkeys); return new IndexBound(getIndexExpressionRow(index, pKeys), getIndexColumnSelector(index, nkeys)); } protected IndexKeyRange assembleSpatialIndexKeyRange(SingleIndexScan index, ColumnExpressionToIndex fieldOffsets) { IndexRowType indexRowType = getIndexRowType(index); if (index.getHighComparand() == null) return IndexKeyRange.aroundObject(indexRowType, assembleSpatialIndexObject(index, index.getLowComparand(), fieldOffsets)); else if (index.getLowComparand() == index.getHighComparand()) return IndexKeyRange.spatialObject(indexRowType, assembleSpatialIndexObject(index, index.getLowComparand(), fieldOffsets)); else throw new AkibanInternalException("Unrecognized spatial index " + index); } protected IndexBound assembleSpatialIndexObject(SingleIndexScan index, ExpressionNode spatialExpr, ColumnExpressionToIndex fieldOffsets) { List<ExpressionNode> equalityComparands = index.getEqualityComparands(); int nkeys = 0; if (equalityComparands != null) nkeys = equalityComparands.size(); nkeys += index.getIndex().spatialColumns(); TPreparedExpression[] pkeys = new TPreparedExpression[nkeys]; int kidx = 0; if (equalityComparands != null) { for (ExpressionNode comp : equalityComparands) { if (comp != null) { assembleExpressionInto(comp, fieldOffsets, pkeys, kidx); } kidx++; } } // May have been transformed from original SQL to index key expression. spatialExpr = resolveAddedExpression(spatialExpr, planContext); assembleExpressionInto(spatialExpr, fieldOffsets, pkeys, kidx++); return getIndexBound(index.getIndex(), pkeys, nkeys); } /** Return a column selector that enables the first <code>nkeys</code> fields * of a row of the index's user table. */ protected ColumnSelector getIndexColumnSelector(final Index index, final int nkeys) { assert nkeys <= index.getAllColumns().size() : index + " " + nkeys; return new IndexRowPrefixSelector(nkeys); } /** Return a {@link Row} for the given index containing the given * {@link Expression} values. */ protected UnboundExpressions getIndexExpressionRow(Index index, TPreparedExpression[] pKeys) { RowType rowType = schema.indexRowType(index); List<TPreparedExpression> pExprs = Arrays.asList(pKeys); return new RowBasedUnboundExpressions(rowType, pExprs); } // Get the required type for any parameters to the statement. protected BasePlannable.ParameterType[] getParameterTypes(List<ParameterNode> params) { if (params == null) { return null; } int nparams = 0; for (ParameterNode param : params) { if (nparams < param.getParameterNumber() + 1) nparams = param.getParameterNumber() + 1; } TypesTranslator typesTranslator = rulesContext.getTypesTranslator(); BasePlannable.ParameterType[] result = new BasePlannable.ParameterType[nparams]; for (ParameterNode param : params) { int paramNo = param.getParameterNumber(); if (result[paramNo] == null) { DataTypeDescriptor sqlType = param.getType(); TInstance type = (TInstance)param.getUserData(); if (type == null) assert param.isReturnOutputParam() : param; else result[paramNo] = new BasePlannable.ParameterType(sqlType, type); } } return result; } private List<ParameterNode> getParameters() { AST ast = ASTStatementLoader.getAST(planContext); if (ast == null) return null; List<ParameterNode> params = ast.getParameters(); if ((params == null) || params.isEmpty()) return null; return params; } protected RowStream assembleFullTextScan(FullTextScan textScan) { RowStream stream = new RowStream(); FullTextQueryBuilder builder = new FullTextQueryBuilder(textScan.getIndex(), schema.ais(), planContext.getQueryContext()); FullTextQueryExpression queryExpression = assembleFullTextQuery(textScan.getQuery(), builder); stream.operator = builder.scanOperator(queryExpression, textScan.getLimit()); stream.rowType = stream.operator.rowType(); return stream; } protected FullTextQueryExpression assembleFullTextQuery(FullTextQuery query, FullTextQueryBuilder builder) { if (query instanceof FullTextField) { FullTextField field = (FullTextField)query; ExpressionNode key = field.getKey(); TPreparedExpression expr = null; String constant = null; boolean isConstant = false; if (key.isConstant()) { ValueSource valueSource = key.getPreptimeValue().value(); constant = (valueSource == null || valueSource.isNull()) ? null : valueSource.getString(); isConstant = true; } else { expr = assembleExpression(key, null); } switch (field.getType()) { case PARSE: if (isConstant) return builder.parseQuery(field.getIndexColumn(), constant); else return builder.parseQuery(field.getIndexColumn(), expr); case MATCH: if (isConstant) return builder.matchQuery(field.getIndexColumn(), constant); else return builder.matchQuery(field.getIndexColumn(), expr); } } else if (query instanceof FullTextBoolean) { FullTextBoolean bquery = (FullTextBoolean)query; List<FullTextQuery> operands = bquery.getOperands(); List<FullTextQueryExpression> queries = new ArrayList<>(operands.size()); for (FullTextQuery operand : operands) { queries.add(assembleFullTextQuery(operand, builder)); } return builder.booleanQuery(queries, bquery.getTypes()); } throw new UnsupportedSQLException("Full text query " + query, null); } /* Expressions-related */ // Assemble a list of expressions from the given nodes. public List<TPreparedExpression> assembleExpressions(List<ExpressionNode> expressions, ColumnExpressionToIndex fieldOffsets) { List<TPreparedExpression> result = new ArrayList<>(expressions.size()); for (ExpressionNode expr : expressions) { result.add(assembleExpression(expr, fieldOffsets)); } return result; } public void assembleExpressionInto(ExpressionNode expr, ColumnExpressionToIndex fieldOffsets, TPreparedExpression[] arr, int i) { TPreparedExpression result = assembleExpression(expr, fieldOffsets); arr[i] = result; } // Assemble a list of expressions from the given nodes. public List<TPreparedExpression> assembleExpressionsA(List<? extends AnnotatedExpression> expressions, ColumnExpressionToIndex fieldOffsets) { List<TPreparedExpression> result = new ArrayList<>(expressions.size()); for (AnnotatedExpression aexpr : expressions) { result.add(assembleExpression(aexpr.getExpression(), fieldOffsets)); } return result; } // Assemble an expression against the given row offsets. public TPreparedExpression assembleExpression(ExpressionNode expr, ColumnExpressionToIndex fieldOffsets) { ColumnExpressionContext context = getColumnExpressionContext(fieldOffsets); return expressionAssembler.assembleExpression(expr, context, this); } // Assemble an aggregate operator public Operator assembleAggregates(Operator inputOperator, RowType inputRowType, int inputsIndex, AggregateSource aggregateSource) { return expressionAssembler.assembleAggregates(inputOperator, inputRowType, inputsIndex, aggregateSource); } protected List<? extends TPreparedExpression> createNulls(RowType rowType) { int nfields = rowType.nFields(); List<TPreparedExpression> result = new ArrayList<>(nfields); for (int i = 0; i < nfields; ++i) result.add(nullExpression(rowType, i)); return result; } @Override public TPreparedExpression assembleSubqueryExpression(SubqueryExpression sexpr) { ColumnExpressionToIndex fieldOffsets = columnBoundRows.current; RowType outerRowType = null; if (fieldOffsets != null) outerRowType = fieldOffsets.getRowType(); int pos = pushBoundRow(fieldOffsets); PlanNode subquery = sexpr.getSubquery().getQuery(); ExpressionNode expression = null; boolean fieldExpression = false; if ((sexpr instanceof AnyCondition) || (sexpr instanceof SubqueryValueExpression)) { if (subquery instanceof ResultSet) subquery = ((ResultSet)subquery).getInput(); if (subquery instanceof Project) { Project project = (Project)subquery; subquery = project.getInput(); expression = project.getFields().get(0); } else { fieldExpression = true; } } RowStream stream = assembleQuery(subquery); TPreparedExpression innerExpression = null; if (fieldExpression) innerExpression = field(stream.rowType, 0); else if (expression != null) innerExpression = assembleExpression(expression, stream.fieldOffsets); TPreparedExpression result = assembleSubqueryExpression(sexpr, stream.operator, innerExpression, outerRowType, stream.rowType, pos); popBoundRow(); columnBoundRows.current = fieldOffsets; return result; } private TPreparedExpression assembleSubqueryExpression(SubqueryExpression sexpr, Operator operator, TPreparedExpression innerExpression, RowType outerRowType, RowType innerRowType, int bindingPosition) { if (sexpr instanceof ExistsCondition) return existsExpression(operator, outerRowType, innerRowType, bindingPosition); else if (sexpr instanceof AnyCondition) return anyExpression(operator, innerExpression, outerRowType, innerRowType, bindingPosition); else if (sexpr instanceof SubqueryValueExpression) return scalarSubqueryExpression(operator, innerExpression, outerRowType, innerRowType, bindingPosition); else if (sexpr instanceof SubqueryResultSetExpression) return resultSetSubqueryExpression(operator, sexpr.getPreptimeValue(), outerRowType, innerRowType, bindingPosition); else throw new UnsupportedSQLException("Unknown subquery", sexpr.getSQLsource()); } public List<TPreparedExpression> assembleUpdates(TableRowType targetRowType, List<UpdateColumn> updateColumns, ColumnExpressionToIndex fieldOffsets) { List<TPreparedExpression> updates = assembleExpressionsA(updateColumns, fieldOffsets); // Have a list of expressions in the order specified. // Want a list as wide as the target row with Java nulls // for the gaps. // TODO: It might be simpler to have an update function // that knew about column offsets for ordered expressions. TPreparedExpression[] row = array(targetRowType.nFields()); for (int i = 0; i < updateColumns.size(); i++) { UpdateColumn column = updateColumns.get(i); row[column.getColumn().getPosition()] = updates.get(i); } updates = Arrays.asList(row); return updates; } public TPreparedExpression[] createNulls(Index index, int nkeys) { TPreparedExpression[] arr = array(nkeys); fillNulls(index, arr); return arr; } public void fillNulls(Index index, TPreparedExpression[] keys) { List<IndexColumn> indexColumns = index.getAllColumns(); for (int i = 0; i < keys.length; ++i) { if (keys[i] == null) keys[i] = new TNullExpression(indexColumns.get(i).getColumn().getType()); } } public RowType valuesRowType(ExpressionsSource expressionsSource) { TInstance[] types = expressionsSource.getFieldTInstances(); return schema.newValuesType(types); } protected TPreparedExpression existsExpression(Operator operator, RowType outerRowType, RowType innerRowType, int bindingPosition) { return new ExistsSubqueryTExpression(operator, outerRowType, innerRowType, bindingPosition); } protected TPreparedExpression anyExpression(Operator operator, TPreparedExpression innerExpression, RowType outerRowType, RowType innerRowType, int bindingPosition) { return new AnySubqueryTExpression(operator, innerExpression, outerRowType, innerRowType, bindingPosition); } protected TPreparedExpression scalarSubqueryExpression(Operator operator, TPreparedExpression innerExpression, RowType outerRowType, RowType innerRowType, int bindingPosition) { return new ScalarSubqueryTExpression(operator, innerExpression, outerRowType, innerRowType, bindingPosition); } protected TPreparedExpression resultSetSubqueryExpression(Operator operator, TPreptimeValue preptimeValue, RowType outerRowType, RowType innerRowType, int bindingPosition) { return new ResultSetSubqueryTExpression(operator, preptimeValue.type(), outerRowType, innerRowType, bindingPosition); } public TPreparedExpression field(RowType rowType, int position) { return new TPreparedField(rowType.typeAt(position), position); } protected TPreparedExpression[] array(int size) { return new TPreparedExpression[size]; } public Operator ifEmptyNulls(Operator input, RowType rowType, InputPreservationOption inputPreservation) { return API.ifEmpty_Default(input, rowType, createNulls(rowType), inputPreservation); } protected TPreparedExpression nullExpression(RowType rowType, int i) { return new TNullExpression(rowType.typeAt(i)); } public API.Ordering createOrdering() { return API.ordering(); } public ExpressionNode resolveAddedExpression(ExpressionNode expr, PlanContext planContext) { ExpressionRewriteVisitor visitor = TypeResolver.getResolver(planContext); return expr.accept(visitor); } /* Bindings-related state */ protected int nestedBindingsDepth = 0; // boundRows is the dynamic list available. protected Deque<ColumnExpressionToIndex> boundRows = new ArrayDeque<>(); // bindings is complete list of assignments; bindingPositions its inverse. protected List<Object> bindings = new ArrayList<>(); protected Map<Object,Integer> bindingPositions = new HashMap<>(); protected Map<HashTable,RowStream> hashTableLoaders = new HashMap<>(); protected int assignBindingPosition(Object binding) { int position = bindings.size(); bindings.add(binding); bindingPositions.put(binding, position); return position; } protected int getBindingPosition(Object binding) { return bindingPositions.get(binding); } protected void initializeBindings() { // Binding positions start with parameter positions. AST ast = ASTStatementLoader.getAST(planContext); if (ast != null) { List<ParameterNode> params = ast.getParameters(); if (params != null) { for (ParameterNode param : params) { assignBindingPosition(param); } } } } protected static final class NullBoundRow implements ColumnExpressionToIndex { @Override public int getIndex(ColumnExpression column) { return -1; } @Override public RowType getRowType() { return null; } public String toString() { return "null"; } } protected int pushBoundRow(ColumnExpressionToIndex boundRow) { if (boundRow == null) boundRow = new NullBoundRow(); boundRows.push(boundRow); return assignBindingPosition(boundRow); } protected void popBoundRow() { boundRows.pop(); } class ColumnBoundRows implements ColumnExpressionContext { ColumnExpressionToIndex current; @Override public ColumnExpressionToIndex getCurrentRow() { return current; } @Override public Iterable<ColumnExpressionToIndex> getBoundRows() { return boundRows; } @Override public int getBindingPosition(ColumnExpressionToIndex boundRow) { return Assembler.this.getBindingPosition(boundRow); } @Override public RowType getRowType(CreateAs createAs){ return Assembler.this.tableRowType(createAs.getTableSource()); } @Override public RowType getRowType(int tableID){ return schema.tableRowType(tableID); } } ColumnBoundRows columnBoundRows = new ColumnBoundRows(); protected ColumnExpressionContext getColumnExpressionContext(ColumnExpressionToIndex current) { columnBoundRows.current = current; return columnBoundRows; } protected ColumnExpressionToIndex lookupNestedBoundRow(GroupLoopScan scan) { // Find the outside key's binding position. ColumnExpression joinColumn = scan.getOutsideJoinColumn(); for (ColumnExpressionToIndex boundRow : boundRows) { int fieldIndex = boundRow.getIndex(joinColumn); if (fieldIndex >= 0) return boundRow; } throw new AkibanInternalException("Outer loop not found " + scan); } protected ColumnExpressionToIndex lookupNestedBoundRow(TableSource table) { // Find the target table's binding position. for (ColumnExpressionToIndex boundRow : boundRows) { if (boundRowIsForTable(boundRow, table)) { return boundRow; } } throw new AkibanInternalException("Outer loop not found " + table); } protected boolean boundRowIsForTable(ColumnExpressionToIndex boundRow, TableSource table) { return ((boundRow instanceof ColumnSourceFieldOffsets) && (((ColumnSourceFieldOffsets)boundRow).getSource()) == table); } } // Struct for multiple value return from assembly. static class RowStream { Operator operator; RowType rowType; boolean unknownTypesPresent; ColumnExpressionToIndex fieldOffsets; } static abstract class BaseColumnExpressionToIndex implements ColumnExpressionToIndex { protected RowType rowType; BaseColumnExpressionToIndex(RowType rowType) { this.rowType = rowType; } @Override public RowType getRowType() { return rowType; } } // Single table-like source. static class ColumnSourceFieldOffsets extends BaseColumnExpressionToIndex { private ColumnSource source; public ColumnSourceFieldOffsets(ColumnSource source, RowType rowType) { super(rowType); this.source = source; } public ColumnSource getSource() { return source; } public TableSource getTable() { return (TableSource)source; } @Override public int getIndex(ColumnExpression column) { if (column.getTable() == source) return column.getPosition(); else if (source instanceof Project) return ((Project)source).getFields().indexOf(column); else return -1; } @Override public String toString() { return super.toString() + "(" + source + ")"; } } // Index used as field source (e.g., covering). static class IndexFieldOffsets extends BaseColumnExpressionToIndex { private IndexScan index; public IndexFieldOffsets(IndexScan index, RowType rowType) { super(rowType); this.index = index; } @Override // Access field of the index row itself. // (Covering index or condition before lookup.) public int getIndex(ColumnExpression column) { return index.getColumns().indexOf(column); } @Override public String toString() { return super.toString() + "(" + index + ")"; } } // Flattened row. static class Flattened extends BaseColumnExpressionToIndex { Map<TableSource,Integer> tableOffsets = new HashMap<>(); int nfields; Flattened() { super(null); // Worked out later. } public void setRowType(RowType rowType) { this.rowType = rowType; } @Override public int getIndex(ColumnExpression column) { Integer tableOffset = tableOffsets.get(column.getTable()); if (tableOffset == null) return -1; return tableOffset + column.getPosition(); } public void addTable(RowType rowType, TableSource table) { if (table != null) tableOffsets.put(table, nfields); nfields += rowType.nFields(); } // Tack on another flattened using product rules. public void product(final Flattened other) { List<TableSource> otherTables = new ArrayList<>(other.tableOffsets.keySet()); Collections.sort(otherTables, new Comparator<TableSource>() { @Override public int compare(TableSource x, TableSource y) { return other.tableOffsets.get(x) - other.tableOffsets.get(y); } }); for (int i = 0; i < otherTables.size(); i++) { TableSource otherTable = otherTables.get(i); if (!tableOffsets.containsKey(otherTable)) { tableOffsets.put(otherTable, nfields); // Width in other.tableOffsets. nfields += (((i+1 >= otherTables.size()) ? other.nfields : other.tableOffsets.get(otherTables.get(i+1))) - other.tableOffsets.get(otherTable)); } } } @Override public String toString() { return super.toString() + "(" + tableOffsets.keySet() + ")"; } } }