package com.tesora.dve.sql.transform.strategy.insert;
/*
* #%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.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.TransformException;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.expression.TableKey;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ConstantExpression;
import com.tesora.dve.sql.node.expression.DelegatingLiteralExpression;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.node.expression.FunctionCall;
import com.tesora.dve.sql.node.expression.LiteralExpression;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.node.test.EngineConstant;
import com.tesora.dve.sql.schema.Column;
import com.tesora.dve.sql.schema.DistributionKey;
import com.tesora.dve.sql.schema.DistributionVector;
import com.tesora.dve.sql.schema.LateSortedInsert;
import com.tesora.dve.sql.schema.PEAbstractTable;
import com.tesora.dve.sql.schema.PEColumn;
import com.tesora.dve.sql.schema.PETable;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.Table;
import com.tesora.dve.sql.schema.TriggerEvent;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.statement.dml.InsertIntoValuesStatement;
import com.tesora.dve.sql.statement.dml.InsertStatement;
import com.tesora.dve.sql.statement.dml.ReplaceIntoValuesStatement;
import com.tesora.dve.sql.transform.strategy.FeaturePlannerIdentifier;
import com.tesora.dve.sql.transform.strategy.PlannerContext;
import com.tesora.dve.sql.transform.strategy.TransformFactory;
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.InsertValuesFeatureStep;
import com.tesora.dve.sql.transform.strategy.featureplan.LateSortedInsertFeatureStep;
import com.tesora.dve.sql.util.ListOfPairs;
public class InsertIntoValuesPlanner extends TransformFactory {
@Override
public FeatureStep plan(DMLStatement stmt, PlannerContext context)
throws PEException {
if (!(stmt instanceof InsertIntoValuesStatement))
return null;
InsertIntoValuesStatement iivs = (InsertIntoValuesStatement) stmt;
checkForIllegalInsert(context,iivs, iivs instanceof ReplaceIntoValuesStatement);
assertValidDupKey(context.getContext(),iivs);
// first time planning, get the value manager to allocate values now - well, unless this is prepare - in which case not so much
// also, we don't do it if we only have late binding constants
boolean allocateAutoIncs = true;
if (context.getContext().getOptions().isPrepare() || context.getContext().getOptions().isTriggerPlanning()
|| context.getApplied().contains(FeaturePlannerIdentifier.INSERT_INTO_VALUES_TRIGGER))
allocateAutoIncs = false;
if (allocateAutoIncs)
context.getContext().getValueManager().handleAutoincrementValues(context.getContext());
return buildInsertIntoValuesFeatureStep(context,this,iivs);
}
@Override
public FeaturePlannerIdentifier getFeaturePlannerID() {
return FeaturePlannerIdentifier.INSERT_VALUES;
}
public static void checkForIllegalInsert(PlannerContext pc, InsertStatement is, boolean notrigs) throws PEException {
Table<?> table = is.getTableInstance().getTable();
if (table.isInfoSchema())
throw new PEException("Cannot insert into info schema table " + table.getName());
PEAbstractTable<?> peat = is.getTableInstance().getAbstractTable();
if (peat.isView())
throw new PEException("No support for updatable views");
if (notrigs && peat.asTable().hasTrigger(pc.getContext(), TriggerEvent.INSERT))
throw new PEException("No support for trigger execution");
}
public static FeatureStep buildInsertIntoValuesFeatureStep(PlannerContext context,
FeaturePlanner planner, InsertIntoValuesStatement iivs) throws PEException {
boolean requiresReferenceTimestamp = iivs.getDerivedInfo().doSetTimestampVariable();
TableInstance intoTI = iivs.getPrimaryTable();
TableKey tk = intoTI.getTableKey();
DistributionVector dv = intoTI.getAbstractTable().getDistributionVector(context.getContext());
FeatureStep out = null;
// since we've already normalized the auto inc ids are already filled in
if (dv.isBroadcast()) {
// will redist to every site, no sense in even bothering with the sorting, breaking up
// well, we still have to if this is an on dup key insert - because we need to figure out what
// the id value is so we can set the last inserted id right
@SuppressWarnings("unchecked")
DistributionKey dk = new DistributionKey(tk, Collections.EMPTY_LIST, null);
out = new InsertValuesFeatureStep(iivs,planner,tk.getAbstractTable().asTable().getStorageGroup(context.getContext()),
dk, requiresReferenceTimestamp,iivs);
} else if (dv.getDistributedWhollyOnTenantColumn(context.getContext()) != null
&& (context.getContext().getPolicyContext().isSchemaTenant() || context.getContext().getPolicyContext().isDataTenant())) {
// so the whole table is distributed on tenant column - so just shove the whole thing down - but first
// build a dist key
PEColumn tenantColumn = intoTI.getAbstractTable().getTenantColumn(context.getContext());
ListOfPairs<PEColumn,ConstantExpression> values = new ListOfPairs<PEColumn,ConstantExpression>();
values.add(tenantColumn,context.getContext().getPolicyContext().getTenantIDLiteral(true));
DistributionKey dk = new DistributionKey(tk, Collections.singletonList(tenantColumn), values);
out = new InsertValuesFeatureStep(iivs,planner,tk.getAbstractTable().asTable().getStorageGroup(context.getContext()),
dk, requiresReferenceTimestamp, iivs);
} else {
ListOfPairs<List<ExpressionNode>, DistributionKey> parts = preparePlanInsert(context, iivs);
if (intoTI.getAbstractTable().getStorageGroup(context.getContext()).isSingleSiteGroup() || parts.size() == 1) {
// push the whole thing down, and just use the first dist key
out = new InsertValuesFeatureStep(iivs,planner,tk.getAbstractTable().asTable().getStorageGroup(context.getContext()),
parts.get(0).getSecond(),requiresReferenceTimestamp, iivs);
} else {
final LateSortedInsert lsi = new LateSortedInsert(iivs, parts);
context.getContext().getValueManager().registerLateSortedInsert(lsi);
if (!context.getContext().getOptions().isPrepare() && !context.getContext().getOptions().isTriggerPlanning())
context.getContext().getValueManager().handleLateSortedInsert(context.getContext(),context.getContext().getValues());
out = new LateSortedInsertFeatureStep(iivs,planner,
tk.getAbstractTable().asTable().getStorageGroup(context.getContext()),
requiresReferenceTimestamp);
}
}
return out;
}
protected void assertValidDupKey(SchemaContext sc, InsertIntoValuesStatement iivs) {
// we're only going to support key = key for now, and only for nonrandom tables
// when the user says key = key, they mean that the inserted key value should win
if (iivs.getOnDuplicateKeyEdge().size() < 1)
return;
DistributionVector dv = iivs.getTable().getDistributionVector(sc);
if (dv.isRandom())
throw new SchemaException(Pass.NORMALIZE,"No support for on duplicate key for random distributed tables");
// we used to have a restriction that there couldn't be more than one tuple, but I don't believe we need that
// since we only accept basically id=id and the use of on dup key means we don't set the update count anyhow,
// it looks like that is a silly restriction
// if (nvals > 1)
// throw new PEException("No support for multivalue insert with on duplicate key");
// last test - we only support id = id and VALUES(id). check for that
for(ExpressionNode expr : iivs.getOnDuplicateKeyEdge()) {
if (EngineConstant.FUNCTION.has(expr, EngineConstant.EQUALS)) {
FunctionCall eq = (FunctionCall) expr;
ExpressionNode lhs = eq.getParametersEdge().get(0);
ExpressionNode rhs = eq.getParametersEdge().get(1);
if (!EngineConstant.COLUMN.has(lhs)) {
throw new SchemaException(Pass.NORMALIZE,"No support for on duplicate key update of a non-column");
}
if (dv.contains(sc, ((ColumnInstance) lhs).getPEColumn()) && !((ColumnInstance) lhs).isSchemaEqual(rhs)) {
throw new SchemaException(Pass.NORMALIZE,"No support for on duplicate key update of distribution column");
}
if (!EngineConstant.COLUMN.has(rhs) && !EngineConstant.FUNCTION.has(rhs) && !EngineConstant.CONSTANT.has(rhs)) {
throw new SchemaException(Pass.NORMALIZE,"Unsupported right-hand side of on duplicate key update");
}
return;
} else {
// not an equals function
break;
}
}
// something more complex
throw new SchemaException(Pass.NORMALIZE,"No support for on duplicate key insert value expression not of form id = id");
}
public static ListOfPairs<List<ExpressionNode>, DistributionKey> preparePlanInsert(PlannerContext pc, InsertIntoValuesStatement iivs) {
SchemaContext sc = pc.getContext();
PETable intoTable = iivs.getPrimaryTable().getAbstractTable().asTable();
List<PEColumn> distTemplate = intoTable.getDistributionVector(sc).getDistributionTemplate(sc);
// the user may have specified the columns in a random order, go figure out the (0 offset) position of each column in the dist template
Map<Column<?>, Integer> columnOffset = new HashMap<Column<?>, Integer>();
for(int i = 0; i < iivs.getColumnSpecificationEdge().size(); i++) {
ExpressionNode e = iivs.getColumnSpecificationEdge().get(i);
if (e instanceof ColumnInstance) {
ColumnInstance cr = (ColumnInstance)e;
if (distTemplate.contains(cr.getColumn()))
columnOffset.put(cr.getColumn(), new Integer(i));
} else {
throw new TransformException(Pass.PLANNER,"Unrecognized expression type in insert colum specification: " + e.getClass().getName());
}
}
ListOfPairs<List<ExpressionNode>, DistributionKey> ret = new ListOfPairs<List<ExpressionNode>, DistributionKey>();
for(List<ExpressionNode> rowValues : iivs.getValues()) {
for(ExpressionNode en : rowValues)
en.setParent(null);
// columns in hand, I can pull out the dv vector
ListOfPairs<PEColumn,ConstantExpression> values = new ListOfPairs<PEColumn,ConstantExpression>();
for(Map.Entry<Column<?>, Integer> offset : columnOffset.entrySet()) {
ExpressionNode e = rowValues.get(offset.getValue());
if (e instanceof ConstantExpression) {
ConstantExpression ce = (ConstantExpression) e;
// this should be interpreted as the sql type of the column
if (e instanceof LiteralExpression) {
LiteralExpression le = (LiteralExpression)e;
if (le instanceof DelegatingLiteralExpression) {
DelegatingLiteralExpression dle = (DelegatingLiteralExpression) le;
sc.getValueManager().setLiteralType(dle, offset.getKey().getType());
}
}
values.add((PEColumn)offset.getKey(), ce);
} else {
throw new TransformException(Pass.PLANNER, "Nonliteral insert value: '" + e.getClass().getName());
}
}
DistributionKey dk = intoTable.getDistributionVector(sc).buildDistKey(sc, iivs.getPrimaryTable().getTableKey(), values);
ret.add(rowValues,dk);
}
return ret;
}
}