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.LinkedHashMap;
import java.util.List;
import java.util.Map;
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.TableKey;
import com.tesora.dve.sql.expression.TriggerTableKey;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.node.expression.FunctionCall;
import com.tesora.dve.sql.node.expression.LateBindingConstantExpression;
import com.tesora.dve.sql.node.structural.FromTableReference;
import com.tesora.dve.sql.node.structural.SortingSpecification;
import com.tesora.dve.sql.schema.FunctionName;
import com.tesora.dve.sql.schema.PEColumn;
import com.tesora.dve.sql.schema.PEKey;
import com.tesora.dve.sql.schema.PETableTriggerPlanningEventInfo;
import com.tesora.dve.sql.schema.TriggerEvent;
import com.tesora.dve.sql.schema.TriggerTime;
import com.tesora.dve.sql.statement.dml.AliasInformation;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.statement.dml.SelectStatement;
import com.tesora.dve.sql.statement.dml.UpdateStatement;
import com.tesora.dve.sql.transform.CopyVisitor;
import com.tesora.dve.sql.transform.SchemaMapper;
import com.tesora.dve.sql.transform.strategy.FeaturePlannerIdentifier;
import com.tesora.dve.sql.transform.strategy.PlannerContext;
import com.tesora.dve.sql.transform.strategy.UpdateRewriteTransformFactory;
import com.tesora.dve.sql.transform.strategy.featureplan.FeatureStep;
import com.tesora.dve.sql.util.ListOfPairs;
import com.tesora.dve.sql.util.ListSet;
import com.tesora.dve.sql.util.Pair;
public class UpdateTriggerPlanner extends TriggerPlanner {
@Override
public FeatureStep plan(DMLStatement stmt, PlannerContext context)
throws PEException {
UpdateStatement us = (UpdateStatement) stmt;
ListOfPairs<ColumnKey,ExpressionNode> updateExprs = UpdateRewriteTransformFactory.getUpdateExpressions(us);
final TableKey updateTable = UpdateRewriteTransformFactory.getUpdateTables(updateExprs);
PETableTriggerPlanningEventInfo triggerInfo = getTriggerInfo(context,updateTable, TriggerEvent.UPDATE);
if (triggerInfo == null)
return null;
LinkedHashMap<ColumnKey,Integer> updateExprOffsets = new LinkedHashMap<ColumnKey,Integer>();
LinkedHashMap<PEColumn,Integer> uniqueKeyOffsets = new LinkedHashMap<PEColumn,Integer>();
Pair<TriggerValueHandlers,SelectStatement> srcSelect = buildTempTableSelect(context,us,updateTable,triggerInfo,updateExprOffsets, uniqueKeyOffsets);
UpdateStatement uniqueKeyUpdate = buildUniqueKeyUpdate(context, updateTable, updateExprOffsets, uniqueKeyOffsets);
return commonPlanning(context,updateTable,srcSelect.getSecond(),srcSelect.getFirst(),uniqueKeyUpdate,triggerInfo);
}
@Override
public FeaturePlannerIdentifier getFeaturePlannerID() {
return FeaturePlannerIdentifier.UPDATE_TRIGGER;
}
private Pair<TriggerValueHandlers,SelectStatement> buildTempTableSelect(PlannerContext pc, UpdateStatement us,
TableKey updatedTable, PETableTriggerPlanningEventInfo triggerInfo,
Map<ColumnKey,Integer> updateExprOffsets, Map<PEColumn,Integer> ukOffsets) throws PEException {
// figure out the unique key we're going to use
PEKey uk = updatedTable.getAbstractTable().asTable().getUniqueKey(pc.getContext());
if (uk == null)
throw new PEException("No support for updating a table with update triggers but no unique key");
ListSet<PEColumn> ukColumns = new ListSet<PEColumn>(uk.getColumns(pc.getContext()));
UpdateStatement copy = CopyVisitor.copy(us);
ListOfPairs<ColumnKey,ExpressionNode> updateExprs = UpdateRewriteTransformFactory.getUpdateExpressions(copy);
SelectStatement out = new SelectStatement(new AliasInformation())
.setTables(copy.getTables())
.setWhereClause(copy.getWhereClause());
out.setOrderBy(copy.getOrderBys());
out.setLimit(copy.getLimit());
out.getDerivedInfo().take(copy.getDerivedInfo());
SchemaMapper mapper = new SchemaMapper(copy.getMapper().getOriginals(), out, copy.getMapper().getCopyContext());
out.setMapper(mapper);
// key is the trigger column key, value is the original column key
HashMap<ColumnKey,ColumnKey> updateKeyForwarding = new HashMap<ColumnKey,ColumnKey>();
// key is orginal column key, value is update expression
LinkedHashMap<ColumnKey,ExpressionNode> updateExprMap = new LinkedHashMap<ColumnKey,ExpressionNode>();
for(Pair<ColumnKey,ExpressionNode> p : updateExprs) {
ColumnKey triggerColumnKey = new ColumnKey(new TriggerTableKey(updatedTable.getTable(),-1,TriggerTime.AFTER),p.getFirst().getPEColumn());
updateKeyForwarding.put(triggerColumnKey,p.getFirst());
updateExprMap.put(p.getFirst(),p.getSecond());
}
List<ExpressionNode> proj = new ArrayList<ExpressionNode>();
List<TriggerValueHandler> types = new ArrayList<TriggerValueHandler>();
Collection<ColumnKey> triggerColumns = triggerInfo.getTriggerBodyColumns(pc.getContext());
for(ColumnKey ck : triggerColumns) {
int position = proj.size();
boolean isKeyPart = false;
TriggerTableKey ttk = (TriggerTableKey) ck.getTableKey();
if (ttk.getTime() == TriggerTime.BEFORE) {
isKeyPart = ukColumns.contains(ck.getPEColumn());
proj.add(new ColumnInstance(ck.getPEColumn(),updatedTable.toInstance()));
types.add(new TriggerValueHandler(ck.getPEColumn().getType()));
} else {
ColumnKey origCK = updateKeyForwarding.get(ck);
if (origCK != null) {
ExpressionNode en = updateExprMap.remove(origCK);
updateExprOffsets.put(origCK,position);
proj.add(en);
} else {
// not updated, so just take the before image
isKeyPart = ukColumns.contains(ck.getPEColumn());
proj.add(new ColumnInstance(ck.getPEColumn(),updatedTable.toInstance()));
}
types.add(new TriggerValueHandler(ck.getPEColumn().getType()));
}
if (isKeyPart) {
Integer any = ukOffsets.get(ck.getPEColumn());
if (any == null)
ukOffsets.put(ck.getPEColumn(), position);
}
}
// proj now has the trigger columns, left to right
// add any unreferenced update exprs
for(Map.Entry<ColumnKey, ExpressionNode> me : updateExprMap.entrySet()) {
updateExprOffsets.put(me.getKey(), proj.size());
proj.add(me.getValue());
types.add(new TriggerValueHandler(me.getKey().getPEColumn().getType()));
}
// finally, if there are any parts of the unique key which are missing, add those too
for(PEColumn pec : ukColumns) {
Integer any = ukOffsets.get(pec);
if (any == null) {
ukOffsets.put(pec, proj.size());
proj.add(new ColumnInstance(pec,updatedTable.toInstance()));
types.add(new TriggerValueHandler(pec.getType()));
}
}
out.setProjection(proj);
return new Pair<TriggerValueHandlers,SelectStatement>(new TriggerValueHandlers(types),out);
}
private UpdateStatement buildUniqueKeyUpdate(PlannerContext context, TableKey updatedTable, LinkedHashMap<ColumnKey,Integer> updateExprOffsets,
LinkedHashMap<PEColumn,Integer> uniqueKeyOffsets) {
// build a new table key for this thing
List<ExpressionNode> updateExprs = new ArrayList<ExpressionNode>();
for(Map.Entry<ColumnKey,Integer> me : updateExprOffsets.entrySet()) {
FunctionCall eq = new FunctionCall(FunctionName.makeEquals(),me.getKey().toInstance(),
new LateBindingConstantExpression(me.getValue(),me.getKey().getColumn().getType()));
updateExprs.add(eq);
}
FromTableReference ftr = new FromTableReference(updatedTable.toInstance());
UpdateStatement out = new UpdateStatement(Collections.singletonList(ftr),
updateExprs,
buildUniqueWhereClause(updatedTable,uniqueKeyOffsets),
Collections.<SortingSpecification> emptyList(),
null,
new AliasInformation(),
null);
return out;
}
}