package com.tesora.dve.sql.schema;
/*
* #%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.Collection;
import java.util.LinkedHashMap;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.sql.expression.ColumnKey;
import com.tesora.dve.sql.node.GeneralCollectingTraversal;
import com.tesora.dve.sql.node.LanguageNode;
import com.tesora.dve.sql.node.Traversal;
import com.tesora.dve.sql.node.expression.CastFunctionCall;
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.expression.LiteralExpression;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.node.expression.TriggerTableInstance;
import com.tesora.dve.sql.node.test.EngineConstant;
import com.tesora.dve.sql.parser.InvokeParser;
import com.tesora.dve.sql.parser.ParserOptions;
import com.tesora.dve.sql.parser.TranslatorInitCallback;
import com.tesora.dve.sql.parser.TranslatorUtils;
import com.tesora.dve.sql.statement.Statement;
import com.tesora.dve.sql.statement.dml.ProjectingStatement;
import com.tesora.dve.sql.statement.dml.SelectStatement;
import com.tesora.dve.sql.statement.dml.UnionStatement;
import com.tesora.dve.sql.transform.strategy.featureplan.FeatureStep;
import com.tesora.dve.sql.transform.strategy.featureplan.NestedPlanFeatureStep;
import com.tesora.dve.sql.util.ListSet;
public class PETableTriggerPlanningEventInfo extends PETableTriggerEventInfo {
// the key iteration order defines the temp table result set
private LinkedHashMap<ColumnKey,Integer> connValueOffsets;
// the feature step that represents the before body
private FeatureStep beforeStep;
// the after step that represents the after body
private FeatureStep afterStep;
public PETableTriggerPlanningEventInfo() {
super();
connValueOffsets = null;
}
private void ensureRuntime(SchemaContext sc) throws PEException {
if (connValueOffsets == null) {
TriggerEvent event = (getBefore() != null ? getBefore().getEvent() : getAfter().getEvent());
TriggerColumnTraversal trav = new TriggerColumnTraversal(event);
FeatureStep beforeStep = null;
FeatureStep afterStep = null;
if (getBefore() != null)
beforeStep = buildStep(sc,trav,getBefore());
if (getAfter() != null)
afterStep = buildStep(sc,trav,getAfter());
if (connValueOffsets == null) {
synchronized(this) {
if (connValueOffsets == null) {
connValueOffsets = trav.getTriggerColumnOffsets();
this.beforeStep = beforeStep;
this.afterStep = afterStep;
}
}
}
}
}
private FeatureStep buildStep(SchemaContext context, TriggerColumnTraversal tct, PETrigger trigger) throws PEException {
// we always make a new context now, because we will build a nested plan
SchemaContext sc = SchemaContext.makeImmutableIndependentContext(context);
sc.setCurrentDatabase(trigger.getTargetTable().getPEDatabase(sc));
// reparse to get the right schema objects, and force all literals to be actual literals
ParserOptions originalOptions = sc.getOptions();
ParserOptions myOpts = originalOptions;
if (myOpts == null)
myOpts = context.getOptions();
if (myOpts == null)
myOpts = ParserOptions.NONE;
myOpts = myOpts.setActualLiterals().setResolve().setIgnoreLocking().setTriggerPlanning().setNestedPlan();
Statement body = InvokeParser.parseTriggerBody(trigger.getBodySource(), sc, myOpts, new ScopeInjector(trigger.getTargetTable())).get(0);
tct.setTime(trigger.getTime());
tct.traverse(body);
FeatureStep planned = body.plan(sc,sc.getBehaviorConfiguration());
return new NestedPlanFeatureStep(planned,sc.getValueManager());
}
@Override
public Collection<ColumnKey> getTriggerBodyColumns(SchemaContext sc) throws PEException {
ensureRuntime(sc);
return connValueOffsets.keySet();
}
@Override
public FeatureStep getBeforeStep(SchemaContext sc) throws PEException {
ensureRuntime(sc);
return beforeStep;
}
@Override
public FeatureStep getAfterStep(SchemaContext sc) throws PEException {
ensureRuntime(sc);
return afterStep;
}
private static class TriggerColumnTraversal extends Traversal {
LinkedHashMap<ColumnKey,Integer> triggerColumnOffsets;
private final TriggerEvent event;
private TriggerTime time;
public TriggerColumnTraversal(TriggerEvent event) {
super(Order.POSTORDER,ExecStyle.ONCE);
triggerColumnOffsets = new LinkedHashMap<ColumnKey,Integer>();
this.event = event;
}
public void setTime(TriggerTime time) {
this.time = time;
}
public LinkedHashMap<ColumnKey,Integer> getTriggerColumnOffsets() {
return triggerColumnOffsets;
}
@Override
public LanguageNode action(LanguageNode in) {
if (EngineConstant.COLUMN.has(in)) {
ColumnInstance ci = (ColumnInstance) in;
TableInstance ti = ci.getTableInstance();
if (ti instanceof TriggerTableInstance) {
final TriggerTableInstance tti = (TriggerTableInstance) ti;
ColumnKey ck = ci.getColumnKey();
// if this is a before insert trigger, we're going to replace the NEW.autoinc columns with OLD.autoinc
// for after insert triggers, we leave it alone.
boolean wrap = false;
if (event == TriggerEvent.INSERT && ck.getPEColumn().isAutoIncrement()) {
if (time == TriggerTime.BEFORE) {
TriggerTableInstance oti = new TriggerTableInstance(tti.getTable(),-1,TriggerTime.BEFORE);
ColumnInstance oci = new ColumnInstance(ck.getPEColumn(),oti);
ck = oci.getColumnKey();
wrap = true;
}
}
Integer any = triggerColumnOffsets.get(ck);
if (any == null) {
any = triggerColumnOffsets.size();
triggerColumnOffsets.put(ck, any);
}
final LateBindingConstantExpression value = new LateBindingConstantExpression(any.intValue(),ci.getColumn().getType());
if (wrap) {
final ExpressionNode zeroLiteral = LiteralExpression.makeLongLiteral(0);
return new FunctionCall(FunctionName.makeIfNull(), value, zeroLiteral);
}
return value;
}
}
return in;
}
}
public static class LateBindingConstantCollector extends GeneralCollectingTraversal {
public LateBindingConstantCollector() {
super(Order.POSTORDER, ExecStyle.ONCE);
}
@Override
public boolean is(LanguageNode ln) {
return (ln instanceof LateBindingConstantExpression);
}
}
public static ListSet<LateBindingConstantExpression> findLateBindingExprs(LanguageNode ln) {
LateBindingConstantCollector collector = new LateBindingConstantCollector();
collector.traverse(ln);
ListSet<LateBindingConstantExpression> out = new ListSet<LateBindingConstantExpression>();
for(LanguageNode iln : collector.getCollected())
out.add((LateBindingConstantExpression) iln);
return out;
}
public static class LateBindingCastingTraversal extends Traversal {
public LateBindingCastingTraversal() {
super(Order.POSTORDER, ExecStyle.ONCE);
}
@Override
public LanguageNode action(LanguageNode in) {
if (in instanceof LateBindingConstantExpression) {
LateBindingConstantExpression lbce = (LateBindingConstantExpression) in;
if (lbce.getType().isStringType()) {
// cast to the string type
return new CastFunctionCall(lbce,new UnqualifiedName(String.format("char(%d)",lbce.getType().getSize())));
}
}
return in;
}
}
public static void forceConstantTypes(ProjectingStatement src) {
if (src instanceof SelectStatement) {
SelectStatement ss = (SelectStatement) src;
new LateBindingCastingTraversal().traverse(ss.getProjectionEdge());
} else {
// union statement
UnionStatement us = (UnionStatement) src;
forceConstantTypes(us.getFromEdge().get());
forceConstantTypes(us.getToEdge().get());
}
}
private static class ScopeInjector extends TranslatorInitCallback {
private final PETable target;
public ScopeInjector(PETable targ) {
this.target = targ;
}
public void onInit(TranslatorUtils utils) {
utils.pushTriggerTable(target);
}
}
}