/* * 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.resolver.command; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.teiid.designer.query.metadata.IQueryMetadataInterface; import org.teiid.designer.query.metadata.IStoredProcedureInfo; import org.teiid.designer.query.sql.lang.ISPParameter; import org.teiid.designer.runtime.version.spi.TeiidServerVersion.Version; import org.teiid.query.mapping.relational.QueryNode; import org.teiid.query.metadata.TempMetadataAdapter; import org.teiid.query.metadata.TempMetadataID; import org.teiid.query.metadata.TempMetadataStore; import org.teiid.query.parser.TeiidNodeFactory; import org.teiid.query.parser.TeiidNodeFactory.ASTNodes; import org.teiid.query.resolver.ProcedureContainerResolver; import org.teiid.query.resolver.QueryResolver; import org.teiid.query.resolver.util.ResolverUtil; import org.teiid.query.resolver.util.ResolverVisitor; import org.teiid.query.sql.lang.Command; import org.teiid.query.sql.lang.GroupContext; import org.teiid.query.sql.lang.ProcedureContainer; import org.teiid.query.sql.lang.SPParameter; import org.teiid.query.sql.lang.StoredProcedure; import org.teiid.query.sql.lang.SubqueryContainer; import org.teiid.query.sql.symbol.Array; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.symbol.Reference; import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor; import org.teiid.runtime.client.Messages; import org.teiid.runtime.client.TeiidClientException; /** */ public class ExecResolver extends ProcedureContainerResolver { /** * @param queryResolver */ public ExecResolver(QueryResolver queryResolver) { super(queryResolver); } /** * @param metadata * @param storedProcedureCommand * @param storedProcedureInfo * @param oldParams * @param namedParameters * @throws TeiidClientException * @throws TeiidComponentException * @throws QueryMetadataException */ private void findCommand7Metadata(IQueryMetadataInterface metadata, StoredProcedure storedProcedureCommand, IStoredProcedureInfo storedProcedureInfo, Collection<SPParameter> oldParams, boolean namedParameters) throws Exception { // Cache original input parameter expressions. Depending on whether // the procedure was parsed with named or unnamed parameters, the keys // for this map will either be the String names of the parameters or // the Integer indices, as entered in the user query Map<Object, Expression> inputExpressions = new HashMap<Object, Expression>(); int adjustIndex = 0; for (SPParameter param : oldParams) { if(param.getExpression() == null) { if (param.getParameterType() == SPParameter.RESULT_SET) { adjustIndex--; //If this was already resolved, just pretend the result set param doesn't exist } continue; } if (namedParameters && param.getParameterType() != SPParameter.RETURN_VALUE) { if (inputExpressions.put(param.getName().toUpperCase(), param.getExpression()) != null) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30138, param.getName().toUpperCase())); } } else { inputExpressions.put(param.getIndex() + adjustIndex, param.getExpression()); } } storedProcedureCommand.clearParameters(); int origInputs = inputExpressions.size(); /* * Take the values set from the stored procedure implementation, and match up with the * types of parameter it is from the metadata and then reset the newly joined parameters * into the stored procedure command. If it is a result set get those columns and place * them into the stored procedure command as well. */ List<SPParameter> metadataParams = storedProcedureInfo.getParameters(); List<SPParameter> clonedMetadataParams = new ArrayList<SPParameter>(metadataParams.size()); int inputParams = 0; int outParams = 0; boolean hasReturnValue = false; for (SPParameter metadataParameter : metadataParams) { if( (metadataParameter.getParameterType()==ISPParameter.ParameterInfo.IN.index()) || (metadataParameter.getParameterType()==ISPParameter.ParameterInfo.INOUT.index())){ inputParams++; } else if (metadataParameter.getParameterType() == ISPParameter.ParameterInfo.OUT.index()) { outParams++; } else if (metadataParameter.getParameterType() == ISPParameter.ParameterInfo.RETURN_VALUE.index()) { hasReturnValue = true; } SPParameter clonedParam = metadataParameter.clone(); clonedMetadataParams.add(clonedParam); storedProcedureCommand.setParameter(clonedParam); } if (storedProcedureCommand.isCalledWithReturn() && !hasReturnValue) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30139, storedProcedureCommand.getGroup())); } if(!namedParameters && (inputParams > inputExpressions.size())) { throw new TeiidClientException(Messages.getString(Messages.ERR.ERR_015_008_0007, inputParams, origInputs, storedProcedureCommand.getGroup())); } // Walk through the resolved parameters and set the expressions from the // input parameters int exprIndex = 1; HashSet<String> expected = new HashSet<String>(); if (storedProcedureCommand.isCalledWithReturn() && hasReturnValue) { for (SPParameter param : clonedMetadataParams) { if (param.getParameterType() == SPParameter.RETURN_VALUE) { Expression expr = inputExpressions.remove(exprIndex++); param.setExpression(expr); } } } for (SPParameter param : clonedMetadataParams) { if(param.getParameterType() == SPParameter.RESULT_SET || param.getParameterType() == SPParameter.RETURN_VALUE) { continue; } if (namedParameters) { String nameKey = param.getParameterSymbol().getShortCanonicalName(); Expression expr = inputExpressions.remove(nameKey); // With named parameters, have to check on optional params and default values if (expr == null && param.getParameterType() != ISPParameter.ParameterInfo.OUT.index()) { expr = ResolverUtil.getDefault(param.getParameterSymbol(), metadata); param.setUsingDefault(true); expected.add(nameKey); } param.setExpression(expr); } else { if(param.getParameterType() == SPParameter.OUT) { continue; } Expression expr = inputExpressions.remove(exprIndex++); param.setExpression(expr); } } // Check for leftovers, i.e. params entered by user w/ wrong/unknown names if (!inputExpressions.isEmpty()) { if (namedParameters) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30141, inputExpressions.keySet(), expected)); } throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID31113, inputParams, origInputs, storedProcedureCommand.getGroup().toString())); } } private void findCommand8Metadata(IQueryMetadataInterface metadata, StoredProcedure storedProcedureCommand, IStoredProcedureInfo storedProcedureInfo, Collection<SPParameter> oldParams, boolean namedParameters) throws Exception { // Cache original input parameter expressions. Depending on whether // the procedure was parsed with named or unnamed parameters, the keys // for this map will either be the String names of the parameters or // the Integer indices, as entered in the user query Map<Integer, Expression> positionalExpressions = new TreeMap<Integer, Expression>(); Map<String, Expression> namedExpressions = new TreeMap<String, Expression>(String.CASE_INSENSITIVE_ORDER); int adjustIndex = 0; for (SPParameter param : oldParams) { if(param.getExpression() == null) { if (param.getParameterType() == SPParameter.RESULT_SET) { adjustIndex--; //If this was already resolved, just pretend the result set param doesn't exist } continue; } if (namedParameters && param.getParameterType() != SPParameter.RETURN_VALUE) { if (namedExpressions.put(param.getParameterSymbol().getShortName(), param.getExpression()) != null) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30138, param.getName())); } } else { positionalExpressions.put(param.getIndex() + adjustIndex, param.getExpression()); } } storedProcedureCommand.clearParameters(); int origInputs = positionalExpressions.size() + namedExpressions.size(); /* * Take the values set from the stored procedure implementation, and match up with the * types of parameter it is from the metadata and then reset the newly joined parameters * into the stored procedure command. If it is a result set get those columns and place * them into the stored procedure command as well. */ List<SPParameter> metadataParams = storedProcedureInfo.getParameters(); List<SPParameter> clonedMetadataParams = new ArrayList<SPParameter>(metadataParams.size()); int inputParams = 0; int optionalParams = 0; int outParams = 0; boolean hasReturnValue = false; boolean optional = false; boolean varargs = false; for (int i = 0; i < metadataParams.size(); i++) { SPParameter metadataParameter = metadataParams.get(i); if( (metadataParameter.getParameterType()==ISPParameter.ParameterInfo.IN.index()) || (metadataParameter.getParameterType()==ISPParameter.ParameterInfo.INOUT.index())){ if (ResolverUtil.hasDefault(metadataParameter.getMetadataID(), metadata) || metadataParameter.isVarArg()) { optional = true; optionalParams++; } else { inputParams++; if (optional) { optional = false; inputParams += optionalParams; optionalParams = 0; } } if (metadataParameter.isVarArg()) { varargs = true; } } else if (metadataParameter.getParameterType() == ISPParameter.ParameterInfo.OUT.index()) { outParams++; /* * TODO: it would consistent to do the following, but it is a breaking change for procedures that have intermixed out params with in. * we may need to revisit this later */ //optional = true; //optionalParams++; } else if (metadataParameter.getParameterType() == ISPParameter.ParameterInfo.RETURN_VALUE.index()) { hasReturnValue = true; } SPParameter clonedParam = metadataParameter.clone(); clonedMetadataParams.add(clonedParam); storedProcedureCommand.setParameter(clonedParam); } if (storedProcedureCommand.isCalledWithReturn() && !hasReturnValue) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30139, storedProcedureCommand.getGroup())); } if(!namedParameters && (inputParams > positionalExpressions.size()) ) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30140, inputParams, inputParams + optionalParams + (varargs?"+":""), origInputs, storedProcedureCommand.getGroup())); //$NON-NLS-1$ //$NON-NLS-2$ } // Walk through the resolved parameters and set the expressions from the // input parameters int exprIndex = 1; HashSet<String> expected = new HashSet<String>(); if (storedProcedureCommand.isCalledWithReturn() && hasReturnValue) { for (SPParameter param : clonedMetadataParams) { if (param.getParameterType() == SPParameter.RETURN_VALUE) { Expression expr = positionalExpressions.remove(exprIndex++); param.setExpression(expr); break; } } } for (SPParameter param : clonedMetadataParams) { if(param.getParameterType() == SPParameter.RESULT_SET || param.getParameterType() == SPParameter.RETURN_VALUE) { continue; } if (namedParameters) { String nameKey = param.getParameterSymbol().getShortName(); Expression expr = namedExpressions.remove(nameKey); // With named parameters, have to check on optional params and default values if (expr == null) { if (param.getParameterType() != ISPParameter.ParameterInfo.OUT.index()) { param.setUsingDefault(true); expected.add(nameKey); if (!param.isVarArg()) { expr = ResolverUtil.getDefault(param.getParameterSymbol(), metadata); } else { //zero length array List<Expression> exprs = new ArrayList<Expression>(0); Array array = createASTNode(ASTNodes.ARRAY); array.setExpressions(exprs); array.setImplicit(true); array.setType(param.getClassType()); expr = array; } } } param.setExpression(expr); } else { Expression expr = positionalExpressions.remove(exprIndex++); if(param.getParameterType() == SPParameter.OUT) { if (expr != null) { boolean isRef = expr instanceof Reference; if (!isRef || exprIndex <= inputParams + 1) { //for backwards compatibility, this should be treated instead as an input exprIndex--; positionalExpressions.put(exprIndex, expr); } else if (isRef) { //mimics the hack that was in PreparedStatementRequest. Reference ref = (Reference)expr; ref.setOptional(true); //may be an out /* * Note that there is a corner case here with out parameters intermixed with optional parameters * there's not a good way around this. */ } } continue; } if (expr == null) { if (!param.isVarArg()) { expr = ResolverUtil.getDefault(param.getParameterSymbol(), metadata); } param.setUsingDefault(true); } if (param.isVarArg()) { List<Expression> exprs = new ArrayList<Expression>(positionalExpressions.size() + 1); if (expr != null) { exprs.add(expr); } exprs.addAll(positionalExpressions.values()); positionalExpressions.clear(); Array array = createASTNode(ASTNodes.ARRAY); array.setExpressions(exprs); array.setImplicit(true); expr = array; } param.setExpression(expr); } } // Check for leftovers, i.e. params entered by user w/ wrong/unknown names if (!namedExpressions.isEmpty()) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30141, namedExpressions.keySet(), expected)); } if (!positionalExpressions.isEmpty()) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID31113, positionalExpressions.size(), origInputs, storedProcedureCommand.getGroup().toString())); } } /** * @see org.teiid.query.resolver.CommandResolver#findCommandMetadata(org.teiid.query.sql.lang.Command, * org.teiid.query.metadata.QueryMetadataInterface) */ private void findCommandMetadata(Command command, TempMetadataStore discoveredMetadata, IQueryMetadataInterface metadata) throws Exception { StoredProcedure storedProcedureCommand = (StoredProcedure) command; IStoredProcedureInfo storedProcedureInfo = null; try { storedProcedureInfo = metadata.getStoredProcedureInfoForProcedure(storedProcedureCommand.getProcedureName()); } catch (Exception e) { String[] parts = storedProcedureCommand.getProcedureName().split("\\.", 2); //$NON-NLS-1$ if (parts.length > 1 && parts[0].equalsIgnoreCase(metadata.getVirtualDatabaseName())) { try { storedProcedureInfo = metadata.getStoredProcedureInfoForProcedure(parts[1]); storedProcedureCommand.setProcedureName(parts[1]); } catch(Exception e1) { } } if (storedProcedureInfo == null) { throw e; } } storedProcedureCommand.setUpdateCount(storedProcedureInfo.getUpdateCount()); storedProcedureCommand.setModelID(storedProcedureInfo.getModelID()); storedProcedureCommand.setProcedureID(storedProcedureInfo.getProcedureID()); storedProcedureCommand.setProcedureCallableName(storedProcedureInfo.getProcedureCallableName()); // Get old parameters as they may have expressions set on them - collect // those expressions to copy later into the resolved parameters Collection<SPParameter> oldParams = storedProcedureCommand.getParameters(); boolean namedParameters = storedProcedureCommand.isDisplayNamedParameters(); // If parameter count is zero, then for the purposes of this method treat that // as if named parameters were used. Even though the StoredProcedure was not // parsed that way, the user may have entered no parameters with the intention // of relying on all default values of all optional parameters. if (oldParams.size() == 0 || (oldParams.size() == 1 && storedProcedureCommand.isCalledWithReturn())) { storedProcedureCommand.setDisplayNamedParameters(true); namedParameters = true; } if (getTeiidVersion().isLessThan(Version.TEIID_8_0)) findCommand7Metadata(metadata, storedProcedureCommand, storedProcedureInfo, oldParams, namedParameters); else findCommand8Metadata(metadata, storedProcedureCommand, storedProcedureInfo, oldParams, namedParameters); // Create temporary metadata that defines a group based on either the stored proc // name or the stored query name - this will be used later during planning String procName = storedProcedureCommand.getProcedureName(); List tempElements = storedProcedureCommand.getProjectedSymbols(); boolean isVirtual = storedProcedureInfo.getQueryPlan() != null; discoveredMetadata.addTempGroup(procName, tempElements, isVirtual); // Resolve tempElements against new metadata GroupSymbol procGroup = createASTNode(ASTNodes.GROUP_SYMBOL); procGroup.setName(storedProcedureInfo.getProcedureCallableName()); procGroup.setProcedure(true); TempMetadataID tid = discoveredMetadata.getTempGroupID(procName); tid.setOriginalMetadataID(storedProcedureCommand.getProcedureID()); procGroup.setMetadataID(tid); storedProcedureCommand.setGroup(procGroup); } /** * @see org.teiid.query.resolver.ProcedureContainerResolver#resolveProceduralCommand(org.teiid.query.sql.lang.Command, org.teiid.query.metadata.TempMetadataAdapter) */ @Override public void resolveProceduralCommand(Command command, TempMetadataAdapter metadata) throws Exception { findCommandMetadata(command, metadata.getMetadataStore(), metadata); //Resolve expressions on input parameters StoredProcedure storedProcedureCommand = (StoredProcedure) command; GroupContext externalGroups = storedProcedureCommand.getExternalGroupContexts(); for (SPParameter param : storedProcedureCommand.getParameters()) { Expression expr = param.getExpression(); if(expr == null) { continue; } for (SubqueryContainer<?> container : ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(expr)) { getQueryResolver().setChildMetadata(container.getCommand(), command); getQueryResolver().resolveCommand(container.getCommand(), metadata.getMetadata()); } try { ResolverVisitor visitor = new ResolverVisitor(expr.getTeiidVersion()); visitor.resolveLanguageObject(expr, null, externalGroups, metadata); } catch (Exception e) { if (!checkForArray(param, expr)) { throw e; } continue; } Class<?> paramType = param.getClassType(); ResolverUtil.setDesiredType(expr, paramType, storedProcedureCommand); // Compare type of parameter expression against parameter type // and add implicit conversion if necessary Class<?> exprType = expr.getType(); if(paramType == null || exprType == null) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30143, storedProcedureCommand.getProcedureName(), param.getName())); } String tgtType = getDataTypeManager().getDataTypeName(paramType); String srcType = getDataTypeManager().getDataTypeName(exprType); Expression result = null; if (param.getParameterType() == SPParameter.RETURN_VALUE || param.getParameterType() == SPParameter.OUT) { if (!ResolverUtil.canImplicitlyConvert(getTeiidVersion(), tgtType, srcType)) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30144, param.getParameterSymbol(), tgtType, srcType)); } } else { try { result = ResolverUtil.convertExpression(expr, tgtType, metadata); } catch (Exception e) { throw new TeiidClientException(e, Messages.gs(Messages.TEIID.TEIID30145, new Object[] { param.getParameterSymbol(), srcType, tgtType})); } param.setExpression(result); } } } /** * The param resolving always constructs an array, which is * not appropriate if passing an array directly * @return */ private boolean checkForArray(SPParameter param, Expression expr) { if (!param.isVarArg() || !(expr instanceof Array)) { return false; } Array array = (Array)expr; if (array.getExpressions().size() == 1) { Expression first = array.getExpressions().get(0); if (first.getType() != null && first.getType() == array.getType()) { param.setExpression(first); return true; } } return false; } @Override protected void resolveGroup(TempMetadataAdapter metadata, ProcedureContainer procCommand) throws Exception { //Do nothing } /** * @throws Exception * @see org.teiid.query.resolver.ProcedureContainerResolver#getPlan(org.teiid.query.metadata.QueryMetadataInterface, org.teiid.query.sql.symbol.GroupSymbol) */ @Override protected String getPlan(IQueryMetadataInterface metadata, GroupSymbol group) throws Exception { IStoredProcedureInfo<SPParameter, QueryNode> storedProcedureInfo = metadata.getStoredProcedureInfoForProcedure(group.getName()); //if there is a query plan associated with the procedure, get it. QueryNode plan = storedProcedureInfo.getQueryPlan(); if (plan.getQuery() == null) { throw new TeiidClientException(Messages.gs(Messages.TEIID.TEIID30146, group)); } return plan.getQuery(); } }