/** * 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 static com.foundationdb.util.Strings.join; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.foundationdb.ais.model.AbstractVisitor; import com.foundationdb.ais.model.Column; import com.foundationdb.ais.model.Sequence; import com.foundationdb.qp.operator.QueryContext; import com.foundationdb.qp.rowtype.TableRowType; import com.foundationdb.server.types.TCast; import com.foundationdb.server.types.TExecutionContext; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.TPreptimeValue; import com.foundationdb.server.types.common.types.TypesTranslator; import com.foundationdb.server.types.service.OverloadResolver; import com.foundationdb.server.types.service.TypesRegistryService; import com.foundationdb.server.types.texpressions.TCastExpression; import com.foundationdb.server.types.texpressions.TPreparedFunction; import com.foundationdb.server.types.texpressions.TPreparedLiteral; import com.foundationdb.server.types.texpressions.TSequenceNextValueExpression; import com.foundationdb.server.types.texpressions.TValidatedScalar; import com.foundationdb.server.types.value.Value; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.foundationdb.ais.model.AkibanInformationSchema; import com.foundationdb.ais.model.PrimaryKey; import com.foundationdb.ais.model.Table; import com.foundationdb.qp.expression.IndexBound; import com.foundationdb.qp.expression.IndexKeyRange; import com.foundationdb.qp.expression.RowBasedUnboundExpressions; import com.foundationdb.qp.operator.API; import com.foundationdb.qp.operator.Operator; import com.foundationdb.qp.operator.API.Ordering; import com.foundationdb.qp.rowtype.IndexRowType; import com.foundationdb.qp.rowtype.RowType; import com.foundationdb.qp.rowtype.Schema; import com.foundationdb.qp.util.SchemaCache; import com.foundationdb.server.api.dml.ColumnSelector; import com.foundationdb.server.explain.ExplainContext; import com.foundationdb.server.explain.format.DefaultFormatter; import com.foundationdb.server.types.texpressions.TPreparedExpression; import com.foundationdb.server.types.texpressions.TPreparedField; import com.foundationdb.server.types.texpressions.TPreparedParameter; /** * Generate one of a static set of internally used operator plans. * @author tjoneslo * */ public class PlanGenerator { private static final Logger logger = LoggerFactory.getLogger(PlanGenerator.class); /** * Scan a group table and pull back all the rows from the group, with their children * Group table equivelent of a full table scan. * * Generates a plan * Filter_default * GroupScan (table group) * * */ public static Operator generateScanPlan (AkibanInformationSchema ais, Table table) { final Schema schema = SchemaCache.globalSchema(ais); Operator plan = API.groupScan_Default(table.getGroup()); final List<RowType> keepTypes = new ArrayList<>(); table.visit(new AbstractVisitor() { @Override public void visit(Table table) { keepTypes.add(schema.tableRowType(table)); } }); plan = API.filter_Default(plan, keepTypes); if (logger.isDebugEnabled()) { DefaultFormatter formatter = new DefaultFormatter(table.getName().getSchemaName()); logger.debug("Scan Plan for {}:\n{}", table, join(formatter.format(plan.getExplainer(new ExplainContext())))); } return plan; } /** * Scan a group starting with Primary Key of a table, * then get all of the children (if any). Primary key * is set as parameters to the query context. * * Generates a plan: * Branch Lookup * Index Scan (table, pk-> ?[, ?...]) */ public static Operator generateBranchPlan (AkibanInformationSchema ais, Table table) { final Operator indexScan = generateIndexScan(ais, table); final Schema schema = SchemaCache.globalSchema(ais); PrimaryKey pkey = table.getPrimaryKeyIncludingInternal(); IndexRowType indexType = schema.indexRowType(pkey.getIndex()); return generateBranchPlan(table, indexScan, indexType); } public static Operator generateBranchPlan (Table table, Operator scan, RowType scanType) { final Schema schema = (Schema)scanType.schema(); final TableRowType tableType = schema.tableRowType(table); final List<TableRowType> tableTypes = new ArrayList<>(); tableTypes.add(tableType); for (RowType rowType : Schema.descendentTypes(tableType, schema.userTableTypes())) { tableTypes.add((TableRowType)rowType); } Operator plan = API.groupLookup_Default(scan, table.getGroup(), scanType, tableTypes, API.InputPreservationOption.DISCARD_INPUT, 1); if (logger.isDebugEnabled()) { DefaultFormatter formatter = new DefaultFormatter(table.getName().getSchemaName()); logger.debug("Branch Plan for {}:\n{}", table, join(formatter.format(plan.getExplainer(new ExplainContext())))); } return plan; } /** * Scan a table starting with the primary key and return the full data row * Generates a plan like * AncestorScan (Table) * IndexScan (table, pk->?[, ?]) */ public static Operator generateAncestorPlan (AkibanInformationSchema ais, Table table) { final Schema schema = SchemaCache.globalSchema(ais); TableRowType tableType = schema.tableRowType(table); List<TableRowType> ancestorType = new ArrayList<>(1); ancestorType.add (tableType); IndexRowType indexType = schema.indexRowType(table.getPrimaryKeyIncludingInternal().getIndex()); Operator indexScan = generateIndexScan (ais, table); Operator lookup = API.groupLookup_Default(indexScan, table.getGroup(), indexType, ancestorType, API.InputPreservationOption.DISCARD_INPUT, 1); if (logger.isDebugEnabled()) { DefaultFormatter formatter = new DefaultFormatter(table.getName().getSchemaName()); logger.debug("Ancestor Plan for {}:\n{}", table, join(formatter.format(lookup.getExplainer(new ExplainContext())))); } return lookup; } /** * Generate an index scan of the table based upon the table's primary key * Values for the scan are set as parameters in the PK order. * @param ais * @param table * @return Operator plan for the Index scan */ private static Operator generateIndexScan (AkibanInformationSchema ais, Table table) { final Schema schema = SchemaCache.globalSchema(ais); PrimaryKey pkey = table.getPrimaryKeyIncludingInternal(); final int nkeys = pkey.getColumns().size(); IndexRowType indexType = schema.indexRowType(pkey.getIndex()); List<TPreparedExpression> pexprs = new ArrayList<>(nkeys); for (int i = 0; i < nkeys; i++) { pexprs.add(new TPreparedParameter(i, indexType.typeAt(i))); } IndexBound bound = new IndexBound(new RowBasedUnboundExpressions(indexType, pexprs), new ColumnSelector() { @Override public boolean includesColumn(int columnPosition) { return columnPosition < nkeys; } }); IndexKeyRange indexRange = IndexKeyRange.bounded(indexType, bound, true, bound, true); Ordering ordering = API.ordering(); for (int i = 0; i < nkeys; i++) { ordering.append(new TPreparedField(indexType.typeAt(i), i), true); // Ascending order } return API.indexScan_Default(indexType, indexRange, ordering); } /** * Generate an expression for a column's default value. {@code expression} may * be {@code null} if none was provided (e.g. from user). */ public static TPreparedExpression generateDefaultExpression(Column column, TPreparedExpression expression, TypesRegistryService typesService, TypesTranslator typesTranslator, QueryContext queryContext) { Sequence sequence = column.getIdentityGenerator(); if ((sequence != null) && Boolean.FALSE.equals(column.getDefaultIdentity())) { // FALSE => ALWAYS, override user value even if present expression = new TSequenceNextValueExpression(column.getType(), sequence); } else if (expression == null) { TInstance type = column.getType(); if (sequence != null) { expression = new TSequenceNextValueExpression(type, sequence); } else if (column.getDefaultFunction() != null) { OverloadResolver<TValidatedScalar> resolver = typesService.getScalarsResolver(); TValidatedScalar overload = resolver.get(column.getDefaultFunction(), Collections.<TPreptimeValue>emptyList()).getOverload(); TInstance dinst = overload.resultStrategy().fixed(false); TPreparedExpression defExpr = new TPreparedFunction(overload, dinst, Collections.<TPreparedExpression>emptyList()); if (!dinst.equals(type)) { TCast tcast = typesService.getCastsResolver().cast(dinst.typeClass(), type.typeClass()); defExpr = new TCastExpression(defExpr, tcast, type); } expression = defExpr; } else { final String defaultValue = column.getDefaultValue(); final Value defaultValueSource; if(defaultValue == null) { defaultValueSource = new Value(type); defaultValueSource.putNull(); } else { TCast cast = type.typeClass().castFromVarchar(); if (cast != null) { defaultValueSource = new Value(type); TInstance valInst = typesTranslator.typeForString(defaultValue); TExecutionContext executionContext = new TExecutionContext(Collections.singletonList(valInst), type, queryContext); cast.evaluate(executionContext, new Value(valInst, defaultValue), defaultValueSource); } else { defaultValueSource = new Value(type, defaultValue); } } expression = new TPreparedLiteral(type, defaultValueSource); } } return expression; } }