/* * 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.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.teiid.api.exception.query.QueryMetadataException; import org.teiid.api.exception.query.QueryPlannerException; import org.teiid.api.exception.query.QueryResolverException; import org.teiid.core.TeiidComponentException; import org.teiid.core.TeiidProcessingException; import org.teiid.core.id.IDGenerator; import org.teiid.language.SQLConstants; import org.teiid.query.analysis.AnalysisRecord; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.optimizer.capabilities.CapabilitiesFinder; import org.teiid.query.optimizer.relational.rules.RuleChooseJoinStrategy; import org.teiid.query.optimizer.relational.rules.RuleMergeCriteria; import org.teiid.query.processor.ProcessorPlan; import org.teiid.query.processor.proc.ForEachRowPlan; import org.teiid.query.processor.proc.ProcedurePlan; import org.teiid.query.processor.relational.RelationalNode; import org.teiid.query.resolver.QueryResolver; import org.teiid.query.resolver.util.ResolverUtil; import org.teiid.query.rewriter.QueryRewriter; import org.teiid.query.sql.LanguageObject; import org.teiid.query.sql.lang.*; import org.teiid.query.sql.navigator.DeepPostOrderNavigator; import org.teiid.query.sql.proc.CommandStatement; import org.teiid.query.sql.proc.CreateProcedureCommand; import org.teiid.query.sql.proc.Statement; import org.teiid.query.sql.proc.TriggerAction; 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.sql.util.SymbolMap; import org.teiid.query.sql.visitor.ExpressionMappingVisitor; import org.teiid.query.sql.visitor.GroupCollectorVisitor; import org.teiid.query.util.CommandContext; public final class TriggerActionPlanner { public ProcessorPlan optimize(ProcedureContainer userCommand, TriggerAction ta, IDGenerator idGenerator, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, AnalysisRecord analysisRecord, CommandContext context) throws QueryMetadataException, TeiidComponentException, QueryResolverException, TeiidProcessingException { //TODO consider caching the plans without using the changing vars QueryRewriter.rewrite(ta, metadata, context, QueryResolver.getVariableValues(userCommand, true, metadata)); QueryCommand query = null; Map<ElementSymbol, Expression> params = new HashMap<ElementSymbol, Expression>(); Map<ElementSymbol, Expression> mapping = QueryResolver.getVariableValues(userCommand, false, metadata); for (Map.Entry<ElementSymbol, Expression> entry : mapping.entrySet()) { entry.setValue(QueryRewriter.rewriteExpression(entry.getValue(), context, metadata)); } if (userCommand instanceof Insert) { Insert insert = (Insert)userCommand; if (insert.getQueryExpression() != null) { query = insert.getQueryExpression(); } else { query = new Query(); ((Query)query).setSelect(new Select(RuleChooseJoinStrategy.createExpressionSymbols(insert.getValues()))); } ProcessorPlan plan = rewritePlan(ta, idGenerator, metadata, capFinder, analysisRecord, context, query, mapping, insert); if (plan != null) { return plan; } } else if (userCommand instanceof Delete) { query = createOldQuery(userCommand, ta, metadata, params); } else if (userCommand instanceof Update) { query = createOldQuery(userCommand, ta, metadata, params); } else { throw new AssertionError(); } for (Map.Entry<ElementSymbol, Expression> entry : mapping.entrySet()) { Expression value = entry.getValue(); params.put(entry.getKey(), value); if (entry.getKey().getGroupSymbol().getShortName().equalsIgnoreCase(SQLConstants.Reserved.NEW) && userCommand instanceof Update) { ((Query)query).getSelect().addSymbol(value); } } ForEachRowPlan result = new ForEachRowPlan(); result.setParams(params); ProcessorPlan queryPlan = QueryOptimizer.optimizePlan(query, metadata, idGenerator, capFinder, analysisRecord, context); result.setQueryPlan(queryPlan); result.setLookupMap(RelationalNode.createLookupMap(query.getProjectedSymbols())); CreateProcedureCommand command = new CreateProcedureCommand(ta.getBlock()); command.setVirtualGroup(ta.getView()); command.setUpdateType(userCommand.getType()); ProcedurePlan rowProcedure = (ProcedurePlan)QueryOptimizer.optimizePlan(command, metadata, idGenerator, capFinder, analysisRecord, context); rowProcedure.setRunInContext(false); result.setRowProcedure(rowProcedure); return result; } /** * look for the simple case of a mapping to a single insert statement trigger action - and reconstruct the plan as a single insert * TODO: need internal primitives for delete/update batching in a loop for delete/update cases */ private ProcessorPlan rewritePlan(TriggerAction ta, IDGenerator idGenerator, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, AnalysisRecord analysisRecord, CommandContext context, QueryCommand query, Map<ElementSymbol, Expression> mapping, Insert insert) throws QueryMetadataException, QueryResolverException, TeiidComponentException, QueryPlannerException { if (ta.getBlock().getStatements().size() != 1) { return null; } Statement s = ta.getBlock().getStatements().get(0); if (!(s instanceof CommandStatement)) { return null; } CommandStatement cs = (CommandStatement)s; if (!(cs.getCommand() instanceof Insert)) { return null; } Insert mapped = (Insert) cs.getCommand(); if (mapped.getQueryExpression() != null) { return null; } if (insert.getQueryExpression() != null) { //use a unique inline view name to make the final remapping easier GroupSymbol group = new GroupSymbol("X"); Collection<GroupSymbol> groups = GroupCollectorVisitor.getGroups(query, true); for (int i = 0; groups.contains(group); i++) { group.setName("X_" + i); } List<Expression> projectedSymbols = query.getProjectedSymbols(); Query queryExpression = QueryRewriter.createInlineViewQuery(group, query, metadata, projectedSymbols); List<Expression> viewSymbols = new ArrayList<Expression>(queryExpression.getSelect().getSymbols()); //switch to the values queryExpression.getSelect().clearSymbols(); List<Expression> values = mapped.getValues(); queryExpression.getSelect().addSymbols(values); values.clear(); //update the mapping to the view symbols for (int i = 0; i < projectedSymbols.size(); i++) { ElementSymbol es = insert.getVariables().get(i); mapping.put(new ElementSymbol(es.getShortName(), new GroupSymbol(SQLConstants.Reserved.NEW)), SymbolMap.getExpression(viewSymbols.get(i))); } //map to the query form - changes references back to element form SymbolMap queryMapping = new SymbolMap(); queryMapping.asUpdatableMap().putAll(mapping); ExpressionMappingVisitor visitor = new RuleMergeCriteria.ReferenceReplacementVisitor(queryMapping); DeepPostOrderNavigator.doVisit(queryExpression.getSelect(), visitor); //now we can return a plan based off a single insert statement mapped.setQueryExpression(queryExpression); return QueryOptimizer.optimizePlan(mapped, metadata, idGenerator, capFinder, analysisRecord, context); } List<Expression> values = mapped.getValues(); SymbolMap queryMapping = new SymbolMap(); queryMapping.asUpdatableMap().putAll(mapping); ExpressionMappingVisitor visitor = new RuleMergeCriteria.ReferenceReplacementVisitor(queryMapping); Select select = new Select(); select.addSymbols(values); DeepPostOrderNavigator.doVisit(select, visitor); values.clear(); for (Expression ex : select.getSymbols()) { values.add(SymbolMap.getExpression(ex)); } return QueryOptimizer.optimizePlan(mapped, metadata, idGenerator, capFinder, analysisRecord, context); } private QueryCommand createOldQuery(ProcedureContainer userCommand, TriggerAction ta, QueryMetadataInterface metadata, Map<ElementSymbol, Expression> params) throws QueryMetadataException, TeiidComponentException { List<ElementSymbol> allSymbols = ResolverUtil.resolveElementsInGroup(ta.getView(), metadata); GroupSymbol old = new GroupSymbol(SQLConstants.Reserved.OLD); GroupSymbol newGroup = new GroupSymbol(SQLConstants.Reserved.NEW); for (ElementSymbol elementSymbol : allSymbols) { ElementSymbol es = elementSymbol.clone(); es.setGroupSymbol(old); params.put(es, elementSymbol); if (userCommand instanceof Update) { //default to old es = elementSymbol.clone(); es.setGroupSymbol(newGroup); params.put(es, elementSymbol); } } ArrayList<Expression> selectSymbols = new ArrayList<Expression>(LanguageObject.Util.deepClone(allSymbols, ElementSymbol.class)); QueryCommand query = new Query(new Select(selectSymbols), new From(Arrays.asList(new UnaryFromClause(ta.getView()))), ((FilteredCommand)userCommand).getCriteria(), null, null); return query; } }