/* * 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.processor.proc; import static org.teiid.query.analysis.AnalysisRecord.*; import java.sql.Clob; import java.sql.SQLException; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.teiid.api.exception.query.QueryProcessingException; import org.teiid.client.plan.PlanNode; import org.teiid.common.buffer.BlockedException; import org.teiid.common.buffer.TupleSource; 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.dqp.internal.process.AuthorizationValidator.CommandType; import org.teiid.dqp.internal.process.Request; import org.teiid.language.SQLConstants.Reserved; import org.teiid.logging.LogManager; import org.teiid.metadata.Column; import org.teiid.query.QueryPlugin; import org.teiid.query.analysis.AnalysisRecord; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.metadata.TempMetadataAdapter; import org.teiid.query.metadata.TempMetadataStore; import org.teiid.query.optimizer.QueryOptimizer; import org.teiid.query.optimizer.capabilities.CapabilitiesFinder; import org.teiid.query.parser.QueryParser; import org.teiid.query.processor.ProcessorPlan; import org.teiid.query.processor.RegisterRequestParameter; import org.teiid.query.processor.proc.CreateCursorResultSetInstruction.Mode; import org.teiid.query.processor.relational.SubqueryAwareRelationalNode; import org.teiid.query.resolver.QueryResolver; import org.teiid.query.rewriter.QueryRewriter; import org.teiid.query.sql.ProcedureReservedWords; import org.teiid.query.sql.lang.Command; import org.teiid.query.sql.lang.Create; import org.teiid.query.sql.lang.DynamicCommand; import org.teiid.query.sql.lang.Insert; import org.teiid.query.sql.lang.Query; import org.teiid.query.sql.lang.SetClause; import org.teiid.query.sql.lang.StoredProcedure; import org.teiid.query.sql.proc.CreateProcedureCommand; 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.sql.util.VariableContext; import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor; import org.teiid.query.util.CommandContext; import org.teiid.query.validator.ValidationVisitor; /** * <p> * Executes a SQL statement, and remove its results from the buffer manager. * Executing this instruction does not modify the values of any of the * variables, hence it's results are not important so they are removed * immediately. * </p> */ public class ExecDynamicSqlInstruction extends ProgramInstruction { private static final int MAX_SQL_LENGTH = 1 << 18; //based roughly on what could be the default max over JDBC // the DynamicCommand private DynamicCommand dynamicCommand; // the idGenerator IDGenerator idGenerator; // the CapabilitiesFinder CapabilitiesFinder capFinder; // the metadata for this plan private QueryMetadataInterface metadata; private boolean returnable; // The parent command CreateProcedureCommand parentProcCommand; private Program dynamicProgram; public ExecDynamicSqlInstruction( CreateProcedureCommand parentProcCommand, DynamicCommand command, QueryMetadataInterface metadata, IDGenerator idGenerator, CapabilitiesFinder capFinder, boolean returnable) { this.parentProcCommand = parentProcCommand; this.dynamicCommand = command; this.metadata = metadata; this.capFinder = capFinder; this.idGenerator = idGenerator; this.returnable = returnable; } /** * <p> * Processing this instruction executes the ProcessorPlan for the command on * the CommandStatement of the update procedure language. Executing this * plan does not effect the values of any of the variables defined as part * of the update procedure and hence the results of the ProcessPlan * execution need not be stored for further processing. The results are * removed from the buffer manager immediately after execution. The program * counter is incremented after execution of the plan. * </p> * * @throws BlockedException * if this processing the plan throws a currentVarContext */ public void process(ProcedurePlan procEnv) throws BlockedException, TeiidComponentException, TeiidProcessingException { VariableContext localContext = procEnv.getCurrentVariableContext(); String query = null; try { Clob value = (Clob)procEnv.evaluateExpression(dynamicCommand.getSql()); if (value == null) { throw new QueryProcessingException(QueryPlugin.Util .getString("ExecDynamicSqlInstruction.0")); //$NON-NLS-1$ } if (value.length() > MAX_SQL_LENGTH) { throw new QueryProcessingException(QueryPlugin.Util .gs(QueryPlugin.Event.TEIID31204, MAX_SQL_LENGTH)); } query = value.getSubString(1, MAX_SQL_LENGTH); LogManager.logTrace(org.teiid.logging.LogConstants.CTX_DQP, new Object[] { "Executing dynamic sql ", query }); //$NON-NLS-1$ Command command = QueryParser.getQueryParser().parseCommand(query); //special handling for dynamic anon blocks if (command instanceof CreateProcedureCommand) { if (dynamicCommand.getIntoGroup() != null || returnable) { //won't work unless we use a different approach than the insert into ... //and the creation of an inline view throw new QueryProcessingException(QueryPlugin.Event.TEIID31250, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31250)); } ((CreateProcedureCommand)command).setResultSetColumns(Collections.EMPTY_LIST); } command.setExternalGroupContexts(dynamicCommand.getExternalGroupContexts()); command.setTemporaryMetadata(dynamicCommand.getTemporaryMetadata().clone()); updateContextWithUsingValues(procEnv, localContext); TempMetadataStore metadataStore = command.getTemporaryMetadata(); if (dynamicCommand.getUsing() != null && !dynamicCommand.getUsing().isEmpty()) { metadataStore.addTempGroup(Reserved.USING, new LinkedList<ElementSymbol>(dynamicCommand.getUsing().getClauseMap().keySet())); GroupSymbol using = new GroupSymbol(Reserved.USING); using.setMetadataID(metadataStore.getTempGroupID(Reserved.USING)); command.addExternalGroupToContext(using); metadataStore.addTempGroup(ProcedureReservedWords.DVARS, new LinkedList<ElementSymbol>(dynamicCommand.getUsing().getClauseMap().keySet())); using = new GroupSymbol(ProcedureReservedWords.DVARS); using.setMetadataID(metadataStore.getTempGroupID(ProcedureReservedWords.DVARS)); command.addExternalGroupToContext(using); } QueryResolver.resolveCommand(command, metadata.getDesignTimeMetadata()); validateDynamicCommand(procEnv, command, value.toString()); // create a new set of variables including vars Map<ElementSymbol, Expression> nameValueMap = createVariableValuesMap(localContext); ValidationVisitor visitor = new ValidationVisitor(); Request.validateWithVisitor(visitor, metadata, command); boolean insertInto = false; boolean updateCommand = false; if (!command.returnsResultSet() && !(command instanceof StoredProcedure)) { if (dynamicCommand.isAsClauseSet()) { if (dynamicCommand.getProjectedSymbols().size() != 1 || ((Expression)dynamicCommand.getProjectedSymbols().get(0)).getType() != DataTypeManager.DefaultDataClasses.INTEGER) { throw new QueryProcessingException(QueryPlugin.Event.TEIID31157, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31157)); } } updateCommand = true; } else if (dynamicCommand.getAsColumns() != null && !dynamicCommand.getAsColumns().isEmpty()) { command = QueryRewriter.createInlineViewQuery(new GroupSymbol("X"), command, metadata, dynamicCommand.getAsColumns()); //$NON-NLS-1$ if (dynamicCommand.getIntoGroup() != null) { Insert insert = new Insert(dynamicCommand.getIntoGroup(), dynamicCommand.getAsColumns(), Collections.emptyList()); insert.setQueryExpression((Query)command); command = insert; insertInto = true; } } //if this is an update procedure, it could reassign variables command = QueryRewriter.rewrite(command, metadata, procEnv.getContext(), command instanceof CreateProcedureCommand?Collections.EMPTY_MAP:nameValueMap); ProcessorPlan commandPlan = QueryOptimizer.optimizePlan(command, metadata, idGenerator, capFinder, AnalysisRecord .createNonRecordingRecord(), procEnv .getContext()); if (command instanceof CreateProcedureCommand && commandPlan instanceof ProcedurePlan) { ((ProcedurePlan)commandPlan).setValidateAccess(procEnv.isValidateAccess()); } CreateCursorResultSetInstruction inst = new CreateCursorResultSetInstruction(null, commandPlan, (insertInto||updateCommand)?Mode.UPDATE:returnable?Mode.HOLD:Mode.NOHOLD) { @Override public void process(ProcedurePlan procEnv) throws BlockedException, TeiidComponentException, TeiidProcessingException { boolean done = true; try { super.process(procEnv); } catch (BlockedException e) { done = false; throw e; } finally { if (done) { procEnv.getContext().popCall(); } } } }; dynamicProgram = new Program(false); dynamicProgram.addInstruction(inst); if (dynamicCommand.getIntoGroup() != null) { String groupName = dynamicCommand.getIntoGroup().getName(); if (!procEnv.getTempTableStore().hasTempTable(groupName, true)) { //create the temp table in the parent scope Create create = new Create(); create.setTable(new GroupSymbol(groupName)); for (ElementSymbol es : (List<ElementSymbol>)dynamicCommand.getAsColumns()) { Column c = new Column(); c.setName(es.getShortName()); c.setRuntimeType(DataTypeManager.getDataTypeName(es.getType())); create.getColumns().add(c); } procEnv.getDataManager().registerRequest(procEnv.getContext(), create, TempMetadataAdapter.TEMP_MODEL.getName(), new RegisterRequestParameter()); } //backwards compatibility to support into with a rowcount if (updateCommand) { Insert insert = new Insert(); insert.setGroup(new GroupSymbol(groupName)); for (ElementSymbol es : (List<ElementSymbol>)dynamicCommand.getAsColumns()) { ElementSymbol col = new ElementSymbol(es.getShortName(), insert.getGroup()); col.setType(es.getType()); insert.addVariable(col); } insert.addValue(new Constant(procEnv.getCurrentVariableContext().getValue(ProcedurePlan.ROWCOUNT))); QueryResolver.resolveCommand(insert, metadata.getDesignTimeMetadata()); TupleSource ts = procEnv.getDataManager().registerRequest(procEnv.getContext(), insert, TempMetadataAdapter.TEMP_MODEL.getName(), new RegisterRequestParameter()); ts.nextTuple(); ts.closeSource(); } } // do a recursion check // Add group to recursion stack if (parentProcCommand.getUpdateType() != Command.TYPE_UNKNOWN) { procEnv.getContext().pushCall(Command.getCommandToken(parentProcCommand.getUpdateType()) + " " + parentProcCommand.getVirtualGroup()); //$NON-NLS-1$ } else { if (parentProcCommand.getVirtualGroup() != null) { procEnv.getContext().pushCall(parentProcCommand.getVirtualGroup().toString()); } } procEnv.push(dynamicProgram); } catch (SQLException e) { Object[] params = {dynamicCommand, dynamicCommand.getSql(), e.getMessage()}; throw new QueryProcessingException(QueryPlugin.Event.TEIID30168, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30168, params)); } catch (TeiidProcessingException e) { Object[] params = {dynamicCommand, query == null?dynamicCommand.getSql():query, e.getMessage()}; throw new QueryProcessingException(QueryPlugin.Event.TEIID30168, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30168, params)); } } /** * @param procEnv * @param localContext * @throws TeiidComponentException * @throws TeiidComponentException * @throws TeiidProcessingException */ private void updateContextWithUsingValues(ProcedurePlan procEnv, VariableContext localContext) throws TeiidComponentException, TeiidProcessingException { if (dynamicCommand.getUsing() != null && !dynamicCommand.getUsing().isEmpty()) { for (SetClause setClause : dynamicCommand.getUsing().getClauses()) { Object assignment = procEnv.evaluateExpression(setClause.getValue()); LogManager.logTrace(org.teiid.logging.LogConstants.CTX_DQP, new Object[] { this, " The using variable ", //$NON-NLS-1$ setClause.getSymbol(), " has value :", assignment }); //$NON-NLS-1$ localContext.setValue(setClause.getSymbol(), assignment); ElementSymbol es = setClause.getSymbol().clone(); es.getGroupSymbol().setShortName(Reserved.USING); localContext.setValue(es, assignment); } } } /** * @param localContext * @return */ private Map<ElementSymbol, Expression> createVariableValuesMap(VariableContext localContext) { Map<ElementSymbol, Object> variableMap = new HashMap<ElementSymbol, Object>(); localContext.getFlattenedContextMap(variableMap); Map<ElementSymbol, Expression> nameValueMap = new HashMap<ElementSymbol, Expression>(variableMap.size()); for (Map.Entry<ElementSymbol, Object> entry : variableMap.entrySet()) { if (entry.getKey() instanceof ElementSymbol) { if (entry.getValue() instanceof Expression) { nameValueMap.put(entry.getKey(), (Expression) entry.getValue()); } else { nameValueMap.put(entry.getKey(), new Constant(entry.getValue(), entry.getKey().getType())); } } } return nameValueMap; } /** * @param procEnv * @param command * @throws TeiidComponentException * @throws QueryProcessingException */ private void validateDynamicCommand(ProcedurePlan procEnv, Command command, String commandString) throws TeiidComponentException, QueryProcessingException { // validate project symbols List dynamicExpectedColumns = dynamicCommand.getAsColumns(); List<Expression> sourceProjectedSymbolList = command.getProjectedSymbols(); if (dynamicExpectedColumns != null && !dynamicExpectedColumns.isEmpty()) { if (dynamicExpectedColumns.size() != sourceProjectedSymbolList.size()) { throw new QueryProcessingException(QueryPlugin.Util .getString("ExecDynamicSqlInstruction.4")); //$NON-NLS-1$ } // If there is only one project symbol, we won't validate the name. Iterator dynamicIter = dynamicExpectedColumns.iterator(); Iterator<Expression> sourceIter = sourceProjectedSymbolList.iterator(); // Check for proper element name and datatype definition in the // dynamic SQL // If the projected symbol list equal to 1, we won't bother checking // the name. while (dynamicIter.hasNext()) { Expression dynamicSymbol = (Expression) dynamicIter.next(); Expression sourceExpr = sourceIter.next(); Class<?> sourceSymbolDatatype = sourceExpr.getType(); // Check if the the dynamic sql element types are equal or // implicitly convertible to the source types Class<?> dynamicType = dynamicSymbol.getType(); String dynamicTypeName = DataTypeManager .getDataTypeName(dynamicType); String sourceTypeName = DataTypeManager .getDataTypeName(sourceSymbolDatatype); if (!dynamicTypeName.equals(sourceTypeName) && // If the types aren't the same, and... !DataTypeManager.isImplicitConversion(sourceTypeName, dynamicTypeName)) { // if there's no implicit // conversion between the two throw new QueryProcessingException(QueryPlugin.Util .getString("ExecDynamicSqlInstruction.6", sourceTypeName, sourceExpr, dynamicTypeName)); //$NON-NLS-1$ } } } CommandContext context = procEnv.getContext(); if (procEnv.isValidateAccess() && !context.getDQPWorkContext().isAdmin() && context.getAuthorizationValidator() != null) { context.getAuthorizationValidator().validate(new String[] {commandString}, command, metadata, context, CommandType.USER); } } /** * Returns a deep clone */ public ExecDynamicSqlInstruction clone() { ExecDynamicSqlInstruction clone = new ExecDynamicSqlInstruction( parentProcCommand, dynamicCommand, metadata, idGenerator, capFinder, returnable); return clone; } public String toString() { return "ExecDynamicSqlInstruction"; //$NON-NLS-1$ } public PlanNode getDescriptionProperties() { PlanNode props = new PlanNode("ExecDynamicSqlInstruction"); //$NON-NLS-1$ props.addProperty(PROP_SQL, dynamicCommand.toString()); return props; } public boolean isReturnable() { return returnable; } public void setReturnable(boolean returnable) { this.returnable = returnable; } @Override public Boolean requiresTransaction(boolean transactionalReads) { Boolean expressionRequires = SubqueryAwareRelationalNode.requiresTransaction(transactionalReads, ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(dynamicCommand.getSql())); if (expressionRequires != null && expressionRequires) { return true; } if (this.dynamicCommand.getUsing() != null) { Boolean setRequires = SubqueryAwareRelationalNode.requiresTransaction(transactionalReads, ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(this.dynamicCommand.getUsing().getClauseMap().values())); if (setRequires == null) { if (expressionRequires == null) { return true; } expressionRequires = null; } else if (setRequires) { return true; } } if ((dynamicCommand.getUpdatingModelCount() < 2 && transactionalReads) || dynamicCommand.getUpdatingModelCount() == 1) { return expressionRequires==null?true:null; } if (dynamicCommand.getUpdatingModelCount() > 1) { return true; } return expressionRequires; } }