/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.query.optimizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.teiid.api.exception.query.QueryMetadataException;
import org.teiid.api.exception.query.QueryParserException;
import org.teiid.api.exception.query.QueryPlannerException;
import org.teiid.api.exception.query.QueryResolverException;
import org.teiid.common.buffer.BlockedException;
import org.teiid.common.buffer.BufferManager;
import org.teiid.common.buffer.TupleBatch;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.id.IDGenerator;
import org.teiid.core.types.DataTypeManager;
import org.teiid.language.SQLConstants;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.Column;
import org.teiid.metadata.Table;
import org.teiid.metadata.Trigger;
import org.teiid.query.QueryPlugin;
import org.teiid.query.analysis.AnalysisRecord;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.optimizer.capabilities.CapabilitiesFinder;
import org.teiid.query.parser.QueryParser;
import org.teiid.query.processor.CollectionTupleSource;
import org.teiid.query.processor.ProcessorDataManager;
import org.teiid.query.processor.ProcessorPlan;
import org.teiid.query.processor.proc.ForEachRowPlan;
import org.teiid.query.processor.proc.ProcedurePlan;
import org.teiid.query.resolver.QueryResolver;
import org.teiid.query.sql.LanguageVisitor;
import org.teiid.query.sql.ProcedureReservedWords;
import org.teiid.query.sql.lang.Command;
import org.teiid.query.sql.proc.CreateProcedureCommand;
import org.teiid.query.sql.proc.TriggerAction;
import org.teiid.query.sql.symbol.Constant;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.query.util.CommandContext;
/**
* Handles the planning of triggers from source events
*/
public final class SourceTriggerActionPlanner implements CommandPlanner {
/**
* TODO: elevate the transaction handling?
*/
public static class CompositeProcessorPlan extends ProcessorPlan {
private List<ProcessorPlan> plans;
private int planIndex;
private boolean open;
private List<String> names;
private Table table;
public CompositeProcessorPlan(List<ProcessorPlan> plans, List<String> names, Table t) {
this.plans = plans;
this.names = names;
this.table = t;
}
@Override
public List getOutputElements() {
return Command.getUpdateCommandSymbol();
}
@Override
public void open() throws TeiidComponentException,
TeiidProcessingException {
}
@Override
public void initialize(CommandContext context,
ProcessorDataManager dataMgr, BufferManager bufferMgr) {
super.initialize(context, dataMgr, bufferMgr);
for (ProcessorPlan plan : plans) {
plan.initialize(context, dataMgr, bufferMgr);
}
}
@Override
public TupleBatch nextBatch() throws BlockedException,
TeiidComponentException, TeiidProcessingException {
while (planIndex < plans.size()) {
try {
if (!open) {
plans.get(planIndex).open();
}
plans.get(planIndex).nextBatch();
plans.get(planIndex).close();
} catch (TeiidProcessingException e) {
LogManager.logWarning(LogConstants.CTX_DQP, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31214, names.get(planIndex), table));
} catch (BlockedException e) {
throw e;
}
//allow other exception types to bubble up
open = false;
planIndex++;
}
TupleBatch batch = new TupleBatch(1, new List<?>[0]);
batch.setTerminationFlag(true);
return batch;
}
@Override
public void close() throws TeiidComponentException {
}
@Override
public ProcessorPlan clone() {
throw new UnsupportedOperationException();
}
}
/**
* Represents a source event as a Command - is localized here
* as it's not directly callable by a user
*/
public static class SourceEventCommand extends Command {
private Table table;
private Object[] oldValues;
private Object[] newValues;
private String[] columnNames;
public SourceEventCommand(Table t, Object[] old, Object[] newValues,
String[] columnNames) {
this.table = t;
this.oldValues = old;
this.newValues = newValues;
this.columnNames = columnNames;
}
@Override
public void acceptVisitor(LanguageVisitor visitor) {
}
@Override
public int getType() {
return TYPE_SOURCE_EVENT;
}
@Override
public Object clone() {
return this;
}
@Override
public List<Expression> getProjectedSymbols() {
return Command.getUpdateCommandSymbol();
}
@Override
public boolean areResultsCachable() {
return false;
}
public Table getTable() {
return table;
}
public Object[] getOldValues() {
return oldValues;
}
public Object[] getNewValues() {
return newValues;
}
public String[] getColumnNames() {
return columnNames;
}
@Override
public String toString() {
return "AFTER EVENT ON " + table; //$NON-NLS-1$
}
}
@Override
public ProcessorPlan optimize(Command command, IDGenerator idGenerator,
QueryMetadataInterface metadata, CapabilitiesFinder capFinder,
AnalysisRecord analysisRecord, CommandContext context)
throws QueryPlannerException, QueryMetadataException,
TeiidComponentException {
SourceEventCommand sec = (SourceEventCommand)command;
Map<Expression, Integer> lookup = new HashMap<Expression, Integer>();
Map<ElementSymbol, Expression> params = new HashMap<ElementSymbol, Expression>();
List<Object> tuple = new ArrayList<Object>();
Map<String, Integer> map = null;
if (sec.getColumnNames() != null) {
map = new TreeMap<String, Integer>(String.CASE_INSENSITIVE_ORDER);
for (String name : sec.getColumnNames()) {
map.put(name, map.size());
}
}
GroupSymbol changingGroup = new GroupSymbol(ProcedureReservedWords.CHANGING);
if (sec.newValues != null) {
GroupSymbol newGroup = new GroupSymbol(SQLConstants.Reserved.NEW);
newGroup.setMetadataID(sec.table);
for (int i = 0; i < sec.getTable().getColumns().size(); i++) {
Column c = sec.getTable().getColumns().get(i);
Integer index = null;
if (map != null) {
index = map.get(c.getName());
} else {
index = i;
}
ElementSymbol newElement = new ElementSymbol(c.getName(), newGroup);
newElement.setMetadataID(c);
ElementSymbol changingElement = new ElementSymbol(c.getName(), changingGroup);
lookup.put(newElement, tuple.size());
lookup.put(changingElement, tuple.size() + 1);
params.put(newElement, newElement);
params.put(changingElement, changingElement);
if (index == null) {
//not changing
tuple.add(new Constant(null));
tuple.add(new Constant(Boolean.FALSE));
} else {
//changing
tuple.add(new Constant(DataTypeManager.convertToRuntimeType(sec.newValues[index], true)));
tuple.add(new Constant(Boolean.TRUE));
}
}
}
if (sec.oldValues != null) {
GroupSymbol oldGroup = new GroupSymbol(SQLConstants.Reserved.OLD);
oldGroup.setMetadataID(sec.table);
for (int i = 0; i < sec.getTable().getColumns().size(); i++) {
Column c = sec.getTable().getColumns().get(i);
Integer index = null;
if (map != null) {
index = map.get(c.getName());
} else {
index = i;
}
ElementSymbol oldElement = new ElementSymbol(c.getName(), oldGroup);
oldElement.setMetadataID(c);
lookup.put(oldElement, tuple.size());
params.put(oldElement, oldElement);
if (index != null) {
tuple.add(new Constant(DataTypeManager.convertToRuntimeType(sec.oldValues[index], true)));
}
}
}
List<ProcessorPlan> plans = new ArrayList<ProcessorPlan>();
List<String> names = new ArrayList<String>();
for (Trigger tr : sec.getTable().getTriggers().values()) {
int updateType = Command.TYPE_UPDATE;
switch (tr.getEvent()) {
case DELETE:
updateType = Command.TYPE_DELETE;
if (sec.newValues != null) {
continue;
}
break;
case INSERT:
updateType = Command.TYPE_INSERT;
if (sec.oldValues != null) {
continue;
}
break;
case UPDATE:
if (sec.oldValues == null || sec.newValues == null) {
continue;
}
break;
}
//create plan
ForEachRowPlan result = new ForEachRowPlan();
result.setParams(params);
TriggerAction parseProcedure;
GroupSymbol gs = new GroupSymbol(sec.table.getFullName());
try {
parseProcedure = (TriggerAction)QueryParser.getQueryParser().parseProcedure(tr.getPlan(), true);
QueryResolver.resolveCommand(parseProcedure, gs, updateType, metadata.getDesignTimeMetadata(), false);
} catch (QueryParserException e) {
//should have been validated
throw new TeiidComponentException(e);
} catch (QueryResolverException e) {
//should have been validated
throw new TeiidComponentException(e);
}
CreateProcedureCommand cpc = new CreateProcedureCommand(parseProcedure.getBlock());
gs.setMetadataID(sec.table);
cpc.setVirtualGroup(gs);
cpc.setUpdateType(updateType);
ProcedurePlan rowProcedure = (ProcedurePlan)QueryOptimizer.optimizePlan(cpc, metadata, idGenerator, capFinder, analysisRecord, context);
rowProcedure.setRunInContext(false);
result.setRowProcedure(rowProcedure);
result.setLookupMap(lookup);
result.setTupleSource(new CollectionTupleSource(Arrays.asList(tuple).iterator()));
plans.add(result);
names.add(tr.getName());
}
return new CompositeProcessorPlan(plans, names, sec.table);
}
}