package com.tesora.dve.sql.transform.strategy.triggers;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.queryplan.TriggerValueHandler;
import com.tesora.dve.queryplan.TriggerValueHandlers;
import com.tesora.dve.sql.expression.ColumnKey;
import com.tesora.dve.sql.expression.TriggerTableKey;
import com.tesora.dve.sql.node.expression.AutoIncrementLiteralExpression;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.node.expression.LateBindingConstantExpression;
import com.tesora.dve.sql.node.expression.LiteralExpression;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.node.expression.TempTableInstance;
import com.tesora.dve.sql.node.expression.TriggerTableInstance;
import com.tesora.dve.sql.schema.DistributionVector.Model;
import com.tesora.dve.sql.schema.PEColumn;
import com.tesora.dve.sql.schema.PETable;
import com.tesora.dve.sql.schema.PETableTriggerPlanningEventInfo;
import com.tesora.dve.sql.schema.TempTable;
import com.tesora.dve.sql.schema.TriggerEvent;
import com.tesora.dve.sql.schema.TriggerTime;
import com.tesora.dve.sql.schema.UnqualifiedName;
import com.tesora.dve.sql.schema.modifiers.ColumnKeyModifier;
import com.tesora.dve.sql.schema.modifiers.ColumnModifier;
import com.tesora.dve.sql.schema.modifiers.ColumnModifierKind;
import com.tesora.dve.sql.statement.dml.AliasInformation;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.statement.dml.InsertIntoValuesStatement;
import com.tesora.dve.sql.statement.dml.SelectStatement;
import com.tesora.dve.sql.transform.behaviors.defaults.DefaultFeaturePlannerFilter;
import com.tesora.dve.sql.transform.behaviors.defaults.DefaultFeatureStepBuilder;
import com.tesora.dve.sql.transform.execution.CreateTempTableExecutionStep;
import com.tesora.dve.sql.transform.execution.ExecutionSequence;
import com.tesora.dve.sql.transform.execution.ExecutionStep;
import com.tesora.dve.sql.transform.execution.TriggerExecutionStep;
import com.tesora.dve.sql.transform.strategy.ExecutionCost;
import com.tesora.dve.sql.transform.strategy.FeaturePlannerIdentifier;
import com.tesora.dve.sql.transform.strategy.PlannerContext;
import com.tesora.dve.sql.transform.strategy.featureplan.FeaturePlanner;
import com.tesora.dve.sql.transform.strategy.featureplan.FeatureStep;
import com.tesora.dve.sql.transform.strategy.featureplan.ProjectingFeatureStep;
import com.tesora.dve.sql.transform.strategy.insert.InsertIntoValuesPlanner;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.ListSet;
import com.tesora.dve.sql.util.UnaryFunction;
public class InsertIntoValuesTriggerPlanner extends TriggerPlanner {
@Override
public FeatureStep plan(DMLStatement stmt, PlannerContext context)
throws PEException {
if (!(stmt instanceof InsertIntoValuesStatement))
return null;
InsertIntoValuesStatement iivs = (InsertIntoValuesStatement) stmt;
PETable intoTable = iivs.getPrimaryTable().getAbstractTable().asTable();
PETableTriggerPlanningEventInfo triggerInfo = (PETableTriggerPlanningEventInfo)
intoTable.getTriggers(context.getContext(), TriggerEvent.INSERT);
if (triggerInfo == null)
return null;
List<ColumnKey> rowTableColumnOrder = buildNormalizedOrder(context,intoTable,triggerInfo);
List<TriggerValueHandler> valueHandlers = Functional.apply(rowTableColumnOrder, new UnaryFunction<TriggerValueHandler,ColumnKey>() {
@Override
public TriggerValueHandler evaluate(ColumnKey object) {
if (object.getPEColumn().isAutoIncrement()) {
return new AutoincrementTriggerValueHandler(object.getPEColumn());
} else {
return new TriggerValueHandler(object.getPEColumn().getType());
}
}
});
TempTable rowsTable = buildRowsTable(context, intoTable, triggerInfo, rowTableColumnOrder);
SelectStatement intent = rowsTable.buildSelect(context.getContext());
ProjectingFeatureStep rowsQuery =
DefaultFeatureStepBuilder.INSTANCE.buildProjectingStep(context, this,
intent,
new ExecutionCost(false,true,null,iivs.getValues().size()),
rowsTable.getStorageGroup(context.getContext()),
intoTable.getPEDatabase(context.getContext()),
rowsTable.getDistributionVector(context.getContext()),
null,
null);
InsertIntoValuesStatement populateInsert = buildPopulateInsert(context, rowsTable, rowTableColumnOrder, iivs);
FeatureStep populateStep =
buildPlan(populateInsert,context.withTransform(getFeaturePlannerID()),
DefaultFeaturePlannerFilter.INSTANCE);
// now, we're going to build the single row insert
InsertIntoValuesStatement singleRowInsert = buildOneTupleInsert(context, intoTable, rowTableColumnOrder);
FeatureStep singleRowStep =
InsertIntoValuesPlanner.buildInsertIntoValuesFeatureStep(context,this,singleRowInsert);
return new InsertIntoValuesTriggerFeatureStep(this,intoTable,rowsQuery,
new TriggerValueHandlers(valueHandlers.toArray(new TriggerValueHandler[0])),
rowsTable,populateStep,singleRowStep,
triggerInfo.getBeforeStep(context.getContext()),
triggerInfo.getAfterStep(context.getContext()));
}
@Override
public FeaturePlannerIdentifier getFeaturePlannerID() {
return FeaturePlannerIdentifier.INSERT_INTO_VALUES_TRIGGER;
}
private List<ColumnKey> buildNormalizedOrder(PlannerContext context, PETable theTable, PETableTriggerPlanningEventInfo info) throws PEException {
ArrayList<ColumnKey> out = new ArrayList<ColumnKey>();
Collection<ColumnKey> triggerColumns = info.getTriggerBodyColumns(context.getContext());
ListSet<PEColumn> usedTriggerColumns = new ListSet<PEColumn>();
for(ColumnKey ck : triggerColumns)
usedTriggerColumns.add(ck.getPEColumn());
ListSet<PEColumn> allColumns = new ListSet<PEColumn>(theTable.getColumns(context.getContext()));
allColumns.removeAll(usedTriggerColumns);
out.addAll(triggerColumns);
TriggerTableInstance tti = new TriggerTableInstance(theTable,-1,TriggerTime.AFTER);
for(PEColumn pec : allColumns) {
out.add(new ColumnInstance(pec,tti).getColumnKey());
}
return out;
}
private TempTable buildRowsTable(PlannerContext context, PETable intoTable,
PETableTriggerPlanningEventInfo info, List<ColumnKey> columnOrder) throws PEException {
// convert orig table columns to temp table columns
List<PEColumn> fields = new ArrayList<PEColumn>();
for(ColumnKey ck : columnOrder) {
PEColumn backing = ck.getPEColumn();
// for this we just do the same type, but make it nullable
List<ColumnModifier> mods = new ArrayList<ColumnModifier>();
mods.add(new ColumnModifier(ColumnModifierKind.NULLABLE));
PEColumn newColumn = PEColumn.buildColumn(context.getContext(),
new UnqualifiedName("_lbc" + fields.size()),
backing.getType(),
mods,
null,
Collections.<ColumnKeyModifier> emptyList());
fields.add(newColumn);
}
return TempTable.buildAdHoc(context.getContext(), intoTable.getPEDatabase(context.getContext()),
fields, Model.BROADCAST, Collections.<PEColumn> emptyList(),
context.getTempGroupManager().getGroup(true),
true);
}
private InsertIntoValuesStatement buildPopulateInsert(PlannerContext context, TempTable rowsTable, List<ColumnKey> rowTableColumnOrder,
InsertIntoValuesStatement orig) throws PEException {
// existing offsets of columns in the tuples
HashMap<PEColumn,Integer> tupleOffsetOf = new HashMap<PEColumn,Integer>();
for(ExpressionNode en : orig.getColumnSpecificationEdge()) {
ColumnInstance ci = (ColumnInstance) en;
tupleOffsetOf.put(ci.getPEColumn(), tupleOffsetOf.size());
}
final TempTableInstance tti = new TempTableInstance(context.getContext(),rowsTable);
List<ExpressionNode> columnSpec = Functional.apply(rowsTable.getColumns(context.getContext()), new UnaryFunction<ExpressionNode,PEColumn>() {
@Override
public ExpressionNode evaluate(PEColumn object) {
return new ColumnInstance(object,tti);
}
});
List<List<ExpressionNode>> orderedValues = new ArrayList<List<ExpressionNode>>();
for(List<ExpressionNode> tuple : orig.getValues()) {
ArrayList<ExpressionNode> newTuple = new ArrayList<ExpressionNode>();
for(ColumnKey ck : rowTableColumnOrder) {
PEColumn col = ck.getPEColumn();
ExpressionNode expr = tuple.get(tupleOffsetOf.get(col));
if (col.isAutoIncrement()) {
TriggerTableKey ttk = (TriggerTableKey) ck.getTableKey();
if (ttk.getTime() == TriggerTime.BEFORE) {
// if the corresponding expr node is not a autoinc literal, toss it in
// if it is an autoinc literal, put in a null
if (expr instanceof AutoIncrementLiteralExpression) {
newTuple.add(LiteralExpression.makeNullLiteral());
} else {
newTuple.add(expr);
}
} else {
// whatever the value is
newTuple.add(expr);
}
} else {
// get the value
newTuple.add(expr);
}
}
orderedValues.add(newTuple);
}
InsertIntoValuesStatement iivs = new InsertIntoValuesStatement(tti,columnSpec,orderedValues,
Collections.<ExpressionNode> emptyList(),new AliasInformation(),null);
iivs.getDerivedInfo().addLocalTable(tti.getTableKey());
return iivs;
}
private InsertIntoValuesStatement buildOneTupleInsert(PlannerContext context,
PETable intoTable, List<ColumnKey> rowTableColumnOrder) throws PEException {
final TableInstance nti = new TableInstance(intoTable,intoTable.getName(),
null, context.getContext().getNextTable(),false);
final HashMap<ColumnKey,Integer> rowOffsets = new HashMap<ColumnKey,Integer>();
for(ColumnKey ck : rowTableColumnOrder)
rowOffsets.put(ck,rowOffsets.size());
List<ExpressionNode> colSpec = Functional.apply(rowTableColumnOrder, new UnaryFunction<ExpressionNode,ColumnKey>() {
@Override
public ExpressionNode evaluate(ColumnKey object) {
return new ColumnInstance(object.getPEColumn(),nti);
}
});
List<ExpressionNode> values = Functional.apply(rowTableColumnOrder, new UnaryFunction<ExpressionNode,ColumnKey>() {
@Override
public ExpressionNode evaluate(ColumnKey object) {
return new LateBindingConstantExpression(rowOffsets.get(object),object.getPEColumn().getType());
}
});
InsertIntoValuesStatement iivs = new InsertIntoValuesStatement(nti,colSpec,Collections.singletonList(values),
Collections.<ExpressionNode> emptyList(),new AliasInformation(),null);
iivs.getDerivedInfo().addLocalTable(nti.getTableKey());
return iivs;
}
protected static class InsertIntoValuesTriggerFeatureStep extends AbstractTriggerFeatureStep {
private final TempTable rowsTable;
private final FeatureStep populateStep;
private final FeatureStep actualStep;
private final FeatureStep beforeStep;
private final FeatureStep afterStep;
public InsertIntoValuesTriggerFeatureStep(FeaturePlanner planner,
PETable actualTable, FeatureStep rowsQuery, TriggerValueHandlers handlers,
TempTable rowsTable, FeatureStep populateStep, FeatureStep actualStep,
FeatureStep beforeStep, FeatureStep afterStep) {
super(planner, actualTable, rowsQuery, handlers);
this.rowsTable = rowsTable;
this.populateStep = populateStep;
this.actualStep = actualStep;
this.beforeStep = beforeStep;
this.afterStep = afterStep;
// make sure the traversal still works
addChild(populateStep);
addChild(actualStep);
if (beforeStep != null)
addChild(beforeStep);
if (afterStep != null)
addChild(afterStep);
}
@Override
public void schedule(PlannerContext sc, ExecutionSequence es,
Set<FeatureStep> scheduled) throws PEException {
// TODO Auto-generated method stub
es.append(new CreateTempTableExecutionStep(rowsTable.getPEDatabase(sc.getContext()),rowsTable.getStorageGroup(sc.getContext()),rowsTable));
populateStep.schedule(sc,es,scheduled);
TriggerExecutionStep step = new TriggerExecutionStep(onTable.getPEDatabase(sc.getContext()),
onTable.getStorageGroup(sc.getContext()),
(ExecutionStep) ExecutionSequence.buildSubSequence(sc, actualStep, es.getPlan()),
(beforeStep == null ? null : ExecutionSequence.buildSubSequence(sc, beforeStep, es.getPlan())),
(afterStep == null ? null : ExecutionSequence.buildSubSequence(sc, afterStep, es.getPlan())),
(ExecutionStep) ExecutionSequence.buildSubSequence(sc, rowQuery, es.getPlan()),
handlers);
es.append(step);
}
}
}