--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.MethodCallNode\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.compile;\r
+\r
+import org.apache.derby.iapi.services.loader.ClassInspector;\r
+\r
+import org.apache.derby.iapi.services.compiler.MethodBuilder;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.types.StringDataValue;\r
+import org.apache.derby.iapi.types.TypeId;\r
+import org.apache.derby.iapi.types.JSQLType;\r
+\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\r
+\r
+import org.apache.derby.iapi.sql.compile.Visitable;\r
+import org.apache.derby.iapi.sql.compile.Visitor;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+import org.apache.derby.iapi.sql.compile.CompilerContext;\r
+\r
+import org.apache.derby.iapi.types.DataTypeDescriptor;\r
+\r
+import org.apache.derby.iapi.sql.compile.TypeCompiler;\r
+import org.apache.derby.catalog.TypeDescriptor;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.reference.JDBC30Translation;\r
+\r
+import org.apache.derby.iapi.store.access.Qualifier;\r
+\r
+import org.apache.derby.iapi.util.JBitSet;\r
+\r
+import org.apache.derby.impl.sql.compile.ExpressionClassBuilder;\r
+import org.apache.derby.catalog.types.RoutineAliasInfo;\r
+\r
+import java.lang.reflect.Modifier;\r
+import java.lang.reflect.Member;\r
+\r
+import java.util.StringTokenizer;\r
+import java.util.Vector;\r
+\r
+/**\r
+ * A MethodCallNode represents a Java method call. Method calls can be done\r
+ * through DML (as expressions) or through the CALL statement.\r
+ *\r
+ */\r
+\r
+abstract class MethodCallNode extends JavaValueNode\r
+{\r
+ /*\r
+ ** Name of the method.\r
+ */\r
+ String methodName;\r
+\r
+ /** The name of the class containing the method. May not be known until bindExpression() has been called.\r
+ * @see #bindExpression\r
+ * @see #getJavaClassName()\r
+ */\r
+ String javaClassName;\r
+ \r
+ /**\r
+ For a procedure or function call\r
+ */\r
+ RoutineAliasInfo routineInfo;\r
+\r
+\r
+ /**\r
+ True if this is an internal call, just used to set up a generated method call.\r
+ */\r
+ boolean internalCall;\r
+\r
+ /**\r
+ For resolution of procedure INOUT/OUT parameters to the primitive\r
+ form, such as int[]. May be null.\r
+ */\r
+ private String[] procedurePrimitiveArrayType;\r
+\r
+ // bound signature of arguments, stated in universal types (JSQLType)\r
+ protected JSQLType[] signature;\r
+\r
+ /*\r
+ ** Parameters to the method, if any. No elements if no parameters.\r
+ */\r
+ protected JavaValueNode[] methodParms;\r
+\r
+ /* The method call */\r
+ protected Member method;\r
+\r
+ protected String actualMethodReturnType;\r
+\r
+ /**\r
+ The parameter types for the resolved method.\r
+ */\r
+ String[] methodParameterTypes;\r
+\r
+ /**\r
+ * Initializer for a MethodCallNode\r
+ *\r
+ * @param methodName The name of the method to call\r
+ */\r
+ public void init(Object methodName)\r
+ {\r
+ this.methodName = (String) methodName;\r
+ }\r
+\r
+ public String getMethodName()\r
+ {\r
+ return methodName;\r
+ }\r
+\r
+ /**\r
+ * @return the name of the class that contains the method, null if not known. It may not be known\r
+ * until this node has been bound.\r
+ */\r
+ public String getJavaClassName()\r
+ {\r
+ return javaClassName;\r
+ }\r
+\r
+ /**\r
+ * Add the parameter list\r
+ *\r
+ * @param parameterList A Vector of the parameters\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void addParms(Vector parameterList) throws StandardException\r
+ {\r
+ methodParms = new JavaValueNode[parameterList.size()];\r
+\r
+ int plSize = parameterList.size();\r
+ for (int index = 0; index < plSize; index++)\r
+ {\r
+ QueryTreeNode qt;\r
+\r
+ qt = (QueryTreeNode) parameterList.elementAt(index);\r
+\r
+ /*\r
+ ** Since we need the parameter to be in Java domain format, put a\r
+ ** SQLToJavaValueNode on top of the parameter node if it is a \r
+ ** SQLValueNode. But if the parameter is already in Java domain \r
+ ** format, then we don't need to do anything.\r
+ */\r
+ if ( ! (qt instanceof JavaValueNode))\r
+ {\r
+ qt = (SQLToJavaValueNode) getNodeFactory().getNode(\r
+ C_NodeTypes.SQL_TO_JAVA_VALUE_NODE, \r
+ qt, \r
+ getContextManager());\r
+ }\r
+\r
+ methodParms[index] = (JavaValueNode) qt;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Prints the sub-nodes of this object. See QueryTreeNode.java for\r
+ * how tree printing is supposed to work.\r
+ *\r
+ * @param depth The depth of this node in the tree\r
+ */\r
+\r
+ public void printSubNodes(int depth)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ int parm;\r
+\r
+ super.printSubNodes(depth);\r
+ if (methodParms != null)\r
+ {\r
+ for (parm = 0; parm < methodParms.length; parm++)\r
+ {\r
+ if (methodParms[parm] != null)\r
+ {\r
+ printLabel(depth, "methodParms[" + parm + "] :");\r
+ methodParms[parm].treePrint(depth + 1);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Convert this object to a String. See comments in QueryTreeNode.java\r
+ * for how this should be done for tree printing.\r
+ *\r
+ * @return This object as a String\r
+ */\r
+\r
+ public String toString()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ return "methodName: " +\r
+ (methodName != null ? methodName : "null") + "\n" +\r
+ super.toString();\r
+ }\r
+ else\r
+ {\r
+ return "";\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Bind this expression. This means binding the sub-expressions,\r
+ * as well as figuring out what the return type is for this expression.\r
+ *\r
+ * @param fromList The FROM list for the query this\r
+ * expression is in, for binding columns.\r
+ * @param subqueryList The subquery list being built as we find SubqueryNodes\r
+ * @param aggregateVector The aggregate vector being built as we find AggregateNodes\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ final void bindParameters(\r
+ FromList fromList, SubqueryList subqueryList,\r
+ Vector aggregateVector) \r
+ throws StandardException\r
+ {\r
+ /* Bind the parameters */\r
+ if (methodParms != null)\r
+ {\r
+ int count = methodParms.length;\r
+\r
+ // with a procedure call the signature\r
+ // is preformed in StaticMethodCall from\r
+ // the procedures signature.\r
+ if (signature == null) \r
+ signature = new JSQLType[ count ];\r
+\r
+ for (int parm = 0; parm < count; parm++)\r
+ {\r
+ if (methodParms[parm] != null)\r
+ {\r
+ methodParms[parm] =\r
+ methodParms[parm].bindExpression(\r
+ fromList, subqueryList, aggregateVector);\r
+\r
+ if (routineInfo == null)\r
+ signature[ parm ] = methodParms[ parm ].getJSQLType();\r
+ \r
+ // prohibit LOB columns/types\r
+ if (signature[parm] != null) {\r
+ String type = signature[parm].getSQLType().getTypeId().getSQLTypeName();\r
+ if (type.equals("BLOB") || type.equals("CLOB") || type.equals("NCLOB")) {\r
+ throw StandardException.newException(SQLState.LOB_AS_METHOD_ARGUMENT_OR_RECEIVER);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Return whether or not all of the parameters to this node are\r
+ * QUERY_INVARIANT or CONSTANT. This is useful for VTIs - a VTI is a candidate\r
+ * for materialization if all of its parameters are QUERY_INVARIANT or CONSTANT\r
+ *\r
+ * @return Whether or not all of the parameters to this node are QUERY_INVARIANT or CONSTANT\r
+ * @exception StandardException thrown on error\r
+ */\r
+ protected boolean areParametersQueryInvariant() throws StandardException\r
+ {\r
+ return (getVariantTypeOfParams() == Qualifier.QUERY_INVARIANT);\r
+ }\r
+\r
+ /**\r
+ * Build parameters for error message and throw the exception when there\r
+ * is no matching signature found.\r
+ *\r
+ * @param receiverTypeName Type name for receiver\r
+ * @param parmTypeNames Type names for parameters as object types\r
+ * @param primParmTypeNames Type names for parameters as primitive types\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ void throwNoMethodFound(String receiverTypeName,\r
+ String[] parmTypeNames,\r
+ String[] primParmTypeNames)\r
+ throws StandardException\r
+ {\r
+ /* Put the parameter type names into a single string */\r
+ StringBuffer parmTypes = new StringBuffer();\r
+ for (int i = 0; i < parmTypeNames.length; i++)\r
+ {\r
+ if (i != 0)\r
+ parmTypes.append(", ");\r
+ /* RESOLVE - shouldn't be using hard coded strings for output */\r
+ parmTypes.append( (parmTypeNames[i].length() != 0 ?\r
+ parmTypeNames[i] :\r
+ "UNTYPED"));\r
+ if ((primParmTypeNames != null) &&\r
+ ! primParmTypeNames[i].equals(parmTypeNames[i])) // has primitive\r
+ parmTypes.append("(" + primParmTypeNames[i] + ")");\r
+ }\r
+\r
+ throw StandardException.newException(SQLState.LANG_NO_METHOD_FOUND, \r
+ receiverTypeName,\r
+ methodName,\r
+ parmTypes);\r
+ }\r
+\r
+ /**\r
+ * Preprocess an expression tree. We do a number of transformations\r
+ * here (including subqueries, IN lists, LIKE and BETWEEN) plus\r
+ * subquery flattening.\r
+ * NOTE: This is done before the outer ResultSetNode is preprocessed.\r
+ *\r
+ * @param numTables Number of tables in the DML Statement\r
+ * @param outerFromList FromList from outer query block\r
+ * @param outerSubqueryList SubqueryList from outer query block\r
+ * @param outerPredicateList PredicateList from outer query block\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void preprocess(int numTables,\r
+ FromList outerFromList,\r
+ SubqueryList outerSubqueryList,\r
+ PredicateList outerPredicateList) \r
+ throws StandardException\r
+ {\r
+ int parm;\r
+\r
+ /* Preprocess the parameters */\r
+ if (methodParms != null)\r
+ {\r
+ for (parm = 0; parm < methodParms.length; parm++)\r
+ {\r
+ if (methodParms[parm] != null)\r
+ {\r
+ methodParms[parm].preprocess(numTables,\r
+ outerFromList,\r
+ outerSubqueryList,\r
+ outerPredicateList);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Categorize this predicate. Initially, this means\r
+ * building a bit map of the referenced tables for each predicate.\r
+ * If the source of this ColumnReference (at the next underlying level) \r
+ * is not a ColumnReference or a VirtualColumnNode then this predicate\r
+ * will not be pushed down.\r
+ *\r
+ * For example, in:\r
+ * select * from (select 1 from s) a (x) where x = 1\r
+ * we will not push down x = 1.\r
+ * NOTE: It would be easy to handle the case of a constant, but if the\r
+ * inner SELECT returns an arbitrary expression, then we would have to copy\r
+ * that tree into the pushed predicate, and that tree could contain\r
+ * subqueries and method calls.\r
+ * RESOLVE - revisit this issue once we have views.\r
+ *\r
+ * @param referencedTabs JBitSet with bit map of referenced FromTables\r
+ * @param simplePredsOnly Whether or not to consider method\r
+ * calls, field references and conditional nodes\r
+ * when building bit map\r
+ *\r
+ * @return boolean Whether or not source.expression is a ColumnReference\r
+ * or a VirtualColumnNode.\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public boolean categorize(JBitSet referencedTabs, boolean simplePredsOnly)\r
+ throws StandardException\r
+ {\r
+ /* We stop here when only considering simple predicates\r
+ * as we don't consider method calls when looking\r
+ * for null invariant predicates.\r
+ */\r
+ if (simplePredsOnly)\r
+ {\r
+ return false;\r
+ }\r
+\r
+ boolean pushable = true;\r
+ int param;\r
+\r
+ if (methodParms != null)\r
+ {\r
+ for (param = 0; param < methodParms.length; param++)\r
+ {\r
+ if (methodParms[param] != null)\r
+ {\r
+ pushable = methodParms[param].categorize(referencedTabs, simplePredsOnly) &&\r
+ pushable;\r
+ }\r
+ }\r
+ }\r
+\r
+ /* We need to push down method call. Then the predicate can be used for start/stop\r
+ * key for index scan. The fact that method call's cost is not predictable and can\r
+ * be expensive doesn't mean we shouldn't push it down. Beetle 4826.\r
+ */\r
+ return pushable;\r
+ }\r
+\r
+ /**\r
+ * Remap all ColumnReferences in this tree to be clones of the\r
+ * underlying expression.\r
+ *\r
+ * @return JavaValueNode The remapped expression tree.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public JavaValueNode remapColumnReferencesToExpressions()\r
+ throws StandardException\r
+ {\r
+ int param;\r
+\r
+ if (methodParms != null)\r
+ {\r
+ for (param = 0; param < methodParms.length; param++)\r
+ {\r
+ if (methodParms[param] != null)\r
+ {\r
+ methodParms[param] =\r
+ methodParms[param].remapColumnReferencesToExpressions();\r
+ }\r
+ }\r
+ }\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * Generate the parameters to the given method call\r
+ *\r
+ * @param acb The ExpressionClassBuilder for the class we're generating\r
+ * @param mb the method the expression will go into\r
+ *\r
+ * @return Count of arguments to the method.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public int generateParameters(ExpressionClassBuilder acb,\r
+ MethodBuilder mb)\r
+ throws StandardException\r
+ {\r
+ int param;\r
+\r
+ String[] expectedTypes = methodParameterTypes;\r
+\r
+ ClassInspector classInspector = getClassFactory().getClassInspector();\r
+\r
+ /* Generate the code for each user parameter, generating the appropriate\r
+ * cast when the passed type needs to get widened to the expected type.\r
+ */\r
+ for (param = 0; param < methodParms.length; param++)\r
+ {\r
+ generateOneParameter( acb, mb, param );\r
+\r
+ // type from the SQL-J expression\r
+ String argumentType = getParameterTypeName( methodParms[param] );\r
+\r
+ // type of the method\r
+ String parameterType = expectedTypes[param];\r
+\r
+ if (!parameterType.equals(argumentType))\r
+ {\r
+ // since we reached here through method resolution\r
+ // casts are only required for primitive types.\r
+ // In any other case the expression type must be assignable\r
+ // to the parameter type.\r
+ if (ClassInspector.primitiveType(parameterType)) {\r
+ mb.cast(parameterType);\r
+ } else {\r
+\r
+ // for a prodcedure\r
+ if (routineInfo != null) {\r
+ continue; // probably should be only for INOUT/OUT parameters.\r
+ }\r
+\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(classInspector.assignableTo(argumentType, parameterType),\r
+ "Argument type " + argumentType + " is not assignable to parameter " + parameterType);\r
+ }\r
+\r
+ /*\r
+ ** Set the parameter type in case the argument type is narrower\r
+ ** than the parameter type.\r
+ */\r
+ mb.upCast(parameterType);\r
+\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ return methodParms.length;\r
+ }\r
+\r
+ static public String getParameterTypeName( JavaValueNode param )\r
+ throws StandardException\r
+ {\r
+ String argumentType;\r
+\r
+ // RESOLVE - shouldn't this logic be inside JavaValueNode ??\r
+ // I.e. once the value is primitive then its java type name is its\r
+ // primitive type name.\r
+ if (param.isPrimitiveType()) { argumentType = param.getPrimitiveTypeName(); }\r
+ else { argumentType = param.getJavaTypeName(); }\r
+\r
+ return argumentType;\r
+ }\r
+\r
+ /**\r
+ * Generate one parameter to the given method call. This method is overriden by\r
+ * RepStaticMethodCallNode.\r
+ *\r
+ * @param acb The ExpressionClassBuilder for the class we're generating\r
+ * @param mb the method the expression will go into\r
+ * @param parameterNumber Identifies which parameter to generate. 0 based.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void generateOneParameter(ExpressionClassBuilder acb,\r
+ MethodBuilder mb,\r
+ int parameterNumber )\r
+ throws StandardException\r
+ {\r
+ methodParms[parameterNumber].generateExpression(acb, mb);\r
+ }\r
+\r
+ /**\r
+ * Set the appropriate type information for a null passed as a parameter.\r
+ * This method is called after method resolution, when a signature was\r
+ * successfully matched.\r
+ *\r
+ * @param parmTypeNames String[] with the java type names for the parameters\r
+ * as declared by the method\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void setNullParameterInfo(String[] parmTypeNames)\r
+ throws StandardException\r
+ {\r
+ for (int i = 0; i < methodParms.length; i++)\r
+ {\r
+ /* null parameters are represented by a java type name of "" */\r
+ if (methodParms[i].getJavaTypeName().equals(""))\r
+ { \r
+ /* Set the type information in the null constant node */\r
+ DataTypeDescriptor dts = DataTypeDescriptor.getSQLDataTypeDescriptor(parmTypeNames[i]);\r
+ ((SQLToJavaValueNode)methodParms[i]).value.setType(dts);\r
+\r
+ /* Set the correct java type name */\r
+ methodParms[i].setJavaTypeName(parmTypeNames[i]);\r
+ signature[i] = methodParms[i].getJSQLType();\r
+ }\r
+ }\r
+ }\r
+\r
+ protected void resolveMethodCall(String javaClassName,\r
+ boolean staticMethod) \r
+ throws StandardException\r
+ {\r
+ // only allow direct method calls through routines and internal SQL.\r
+ if (routineInfo == null && !internalCall)\r
+ {\r
+ // See if we are being executed in an internal context\r
+ if ((getCompilerContext().getReliability() & CompilerContext.INTERNAL_SQL_ILLEGAL) != 0) {\r
+ throw StandardException.newException(SQLState.LANG_SYNTAX_ERROR, javaClassName + (staticMethod ? "::" : ".") + methodName);\r
+ }\r
+ }\r
+\r
+ int count = signature.length;\r
+\r
+ ClassInspector classInspector = getClassFactory().getClassInspector();\r
+\r
+ \r
+ String[] parmTypeNames;\r
+ String[] primParmTypeNames = null;\r
+ boolean[] isParam = getIsParam();\r
+\r
+ boolean hasDynamicResultSets = (routineInfo != null) && (count != 0) && (count != methodParms.length);\r
+\r
+ /*\r
+ ** Find the matching method that is public.\r
+ */\r
+\r
+ int signatureOffset = methodName.indexOf('(');\r
+ \r
+ // support Java signatures by checking if the method name contains a '('\r
+ if (signatureOffset != -1) {\r
+ parmTypeNames = parseValidateSignature(methodName, signatureOffset, hasDynamicResultSets);\r
+ methodName = methodName.substring(0, signatureOffset);\r
+ \r
+ // If the signature is specified then Derby resolves to exactly\r
+ // that method. Setting this flag to false disables the method\r
+ // resolution from automatically optionally repeating the last\r
+ // parameter as needed.\r
+ hasDynamicResultSets = false;\r
+ \r
+ }\r
+ else\r
+ {\r
+ parmTypeNames = getObjectSignature();\r
+ }\r
+ try\r
+ { \r
+ method = classInspector.findPublicMethod(javaClassName,\r
+ methodName,\r
+ parmTypeNames,\r
+ null,\r
+ isParam,\r
+ staticMethod,\r
+ hasDynamicResultSets);\r
+\r
+\r
+ // DB2 LUW does not support Java object types for SMALLINT, INTEGER, BIGINT, REAL, DOUBLE\r
+ // and these are the only types that can map to a primitive or an object type according\r
+ // to SQL part 13. So we never have a second chance match.\r
+ // Also if the DDL specified a signature, then no alternate resolution\r
+ if (signatureOffset == -1 && routineInfo == null) {\r
+\r
+ /* If no match, then retry with combinations of object and\r
+ * primitive types.\r
+ */\r
+ if (method == null)\r
+ {\r
+ primParmTypeNames = getPrimitiveSignature(false);\r
+\r
+ method = classInspector.findPublicMethod(javaClassName,\r
+ methodName,\r
+ parmTypeNames,\r
+ primParmTypeNames,\r
+ isParam,\r
+ staticMethod,\r
+ hasDynamicResultSets);\r
+ }\r
+ }\r
+ }\r
+ catch (ClassNotFoundException e)\r
+ {\r
+ /*\r
+ ** If one of the classes couldn't be found, just act like the\r
+ ** method couldn't be found. The error lists all the class names,\r
+ ** which should give the user enough info to diagnose the problem.\r
+ */\r
+ method = null;\r
+ }\r
+ /* Throw exception if no matching signature found */\r
+ if (method == null)\r
+ {\r
+ throwNoMethodFound(javaClassName, parmTypeNames, primParmTypeNames);\r
+ }\r
+\r
+ String typeName = classInspector.getType(method);\r
+ actualMethodReturnType = typeName;\r
+\r
+ if (routineInfo == null) {\r
+\r
+ /* void methods are only okay for CALL Statements */\r
+ if (typeName.equals("void"))\r
+ {\r
+ if (!forCallStatement)\r
+ throw StandardException.newException(SQLState.LANG_VOID_METHOD_CALL);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ String promoteName = null;\r
+ TypeDescriptor returnType = routineInfo.getReturnType();\r
+ String requiredType;\r
+ if (returnType == null)\r
+ {\r
+ // must have a void method for a procedure call.\r
+ requiredType = "void";\r
+ }\r
+ else\r
+ {\r
+ TypeId returnTypeId = TypeId.getBuiltInTypeId(returnType.getJDBCTypeId());\r
+\r
+ requiredType = returnTypeId.getCorrespondingJavaTypeName();\r
+\r
+ if (!requiredType.equals(typeName)) {\r
+ switch (returnType.getJDBCTypeId()) {\r
+ case java.sql.Types.SMALLINT:\r
+ case java.sql.Types.INTEGER:\r
+ case java.sql.Types.BIGINT:\r
+ case java.sql.Types.REAL:\r
+ case java.sql.Types.DOUBLE:\r
+ TypeCompiler tc = getTypeCompiler(returnTypeId);\r
+ requiredType = tc.getCorrespondingPrimitiveTypeName();\r
+ if (!routineInfo.calledOnNullInput() && \r
+ routineInfo.getParameterCount() != 0) {\r
+ promoteName = returnTypeId.getCorrespondingJavaTypeName();\r
+ }\r
+ \r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (!requiredType.equals(typeName))\r
+ {\r
+ throwNoMethodFound(requiredType + " " + javaClassName, parmTypeNames, primParmTypeNames);\r
+ }\r
+\r
+ // for a returns null on null input with a primitive\r
+ // type we need to promote to an object so we can return null.\r
+ if (promoteName != null)\r
+ typeName = promoteName;\r
+ //propogate collation type from RoutineAliasInfo to\r
+ // MethodCallNode DERBY-2972\r
+ if (routineInfo.getReturnType() != null)\r
+ setCollationType(routineInfo.getReturnType().getCollationType()); \r
+ }\r
+ setJavaTypeName( typeName );\r
+ \r
+ methodParameterTypes = classInspector.getParameterTypes(method);\r
+\r
+ for (int i = 0; i < methodParameterTypes.length; i++)\r
+ {\r
+ String methodParameter = methodParameterTypes[i];\r
+\r
+ if (routineInfo != null) {\r
+ if (i < routineInfo.getParameterCount()) {\r
+ int parameterMode = routineInfo.getParameterModes()[i];\r
+\r
+ switch (parameterMode) {\r
+ case JDBC30Translation.PARAMETER_MODE_IN:\r
+ break;\r
+ case JDBC30Translation.PARAMETER_MODE_IN_OUT:\r
+ // we need to see if the type of the array is\r
+ // primitive, not the array itself.\r
+ methodParameter = methodParameter.substring(0, methodParameter.length() - 2);\r
+ break;\r
+\r
+ case JDBC30Translation.PARAMETER_MODE_OUT:\r
+ // value is not obtained *from* parameter.\r
+ continue;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (ClassInspector.primitiveType(methodParameter))\r
+ methodParms[i].castToPrimitive(true);\r
+ }\r
+\r
+ /* Set type info for any null parameters */\r
+ if ( someParametersAreNull() )\r
+ {\r
+ setNullParameterInfo(methodParameterTypes);\r
+ }\r
+\r
+\r
+ \r
+ /* bug 4450 - if the callable statement is ? = call form, generate the metadata\r
+ infor for the return parameter. We don't really need that info in order to\r
+ execute the callable statement. But with jdbc3.0, this information should be\r
+ made available for return parameter through ParameterMetaData class.\r
+ Parser sets a flag in compilercontext if ? = call. If the flag is set,\r
+ we generate the metadata info for the return parameter and reset the flag\r
+ in the compilercontext for future call statements*/\r
+ DataTypeDescriptor dts = DataTypeDescriptor.getSQLDataTypeDescriptor(typeName);\r
+ if (getCompilerContext().getReturnParameterFlag()) {\r
+ getCompilerContext().getParameterTypes()[0] = dts;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Parse the user supplied signature for a method and validate\r
+ * it, need to match the number of parameters passed in and match\r
+ * the valid types for the parameter.\r
+ * @param offset Character offset of first paren\r
+ * @param hasDynamicResultSets Can ResultSet[] parameters be specified.\r
+ * @return The valid array of types for resolution.\r
+ * @throws StandardException\r
+ */\r
+ private String[] parseValidateSignature(String externalName, int offset,\r
+ boolean hasDynamicResultSets)\r
+ throws StandardException\r
+ {\r
+ int siglen = externalName.length();\r
+\r
+ // Ensure the opening paren is not the last\r
+ // character and that the last character is a close paren\r
+ if (((offset + 1) == siglen)\r
+ || (externalName.charAt(siglen - 1) != ')'))\r
+ throw StandardException.newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid\r
+ \r
+ StringTokenizer st = new StringTokenizer(externalName.substring(offset + 1, siglen - 1), ",", true);\r
+ \r
+ String[] signatureTypes = new String[signature.length];\r
+ int count;\r
+ boolean seenClass = false;\r
+ for (count = 0; st.hasMoreTokens();)\r
+ {\r
+ String type = st.nextToken().trim();\r
+ \r
+ // check sequence is <class><comma>class> etc.\r
+ if (",".equals(type))\r
+ {\r
+ if (!seenClass)\r
+ throw StandardException.newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid\r
+ seenClass = false;\r
+ continue;\r
+ }\r
+ else\r
+ {\r
+ if (type.length() == 0)\r
+ throw StandardException.newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid\r
+ seenClass = true;\r
+ count++;\r
+ }\r
+ \r
+ if (count > signature.length)\r
+ {\r
+ if (hasDynamicResultSets)\r
+ {\r
+ // Allow any number of dynamic result set holders\r
+ // but they must match the exact type.\r
+ String rsType = signature[signature.length - 1].getSQLType().\r
+ getTypeId().getCorrespondingJavaTypeName();\r
+ \r
+ if (!type.equals(rsType))\r
+ throw StandardException.newException(SQLState.LANG_DATA_TYPE_GET_MISMATCH, \r
+ type, rsType);\r
+\r
+ if (signatureTypes.length == signature.length)\r
+ {\r
+ // expand once\r
+ String[] sigs = new String[st.countTokens()];\r
+ System.arraycopy(signatureTypes, 0, sigs, 0, signatureTypes.length);\r
+ signatureTypes = sigs;\r
+ }\r
+ \r
+ signatureTypes[count - 1] = type;\r
+ continue;\r
+ \r
+ }\r
+ throw StandardException.newException(SQLState.SQLJ_SIGNATURE_PARAMETER_COUNT, \r
+ Integer.toString(count),\r
+ Integer.toString(signature.length)); // too many types\r
+ }\r
+\r
+ \r
+ TypeId paramTypeId = signature[count - 1].getSQLType().getTypeId();\r
+ \r
+ // Does it match the object name\r
+ if (type.equals(paramTypeId.getCorrespondingJavaTypeName()))\r
+ {\r
+ signatureTypes[count - 1] = type;\r
+ continue;\r
+ }\r
+ \r
+ // how about the primitive name\r
+ if ((paramTypeId.isNumericTypeId() && !paramTypeId.isDecimalTypeId())\r
+ || paramTypeId.isBooleanTypeId())\r
+ {\r
+ TypeCompiler tc = getTypeCompiler(paramTypeId);\r
+ if (type.equals(tc.getCorrespondingPrimitiveTypeName()))\r
+ {\r
+ signatureTypes[count - 1] = type;\r
+ continue; \r
+ }\r
+ }\r
+ throw StandardException.newException(SQLState.LANG_DATA_TYPE_GET_MISMATCH, \r
+ type, paramTypeId.getSQLTypeName()); // type conversion error\r
+ }\r
+ \r
+ // Did signature end with trailing comma?\r
+ if (count != 0 && !seenClass)\r
+ throw StandardException.newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid\r
+ \r
+ if (count < signatureTypes.length)\r
+ {\r
+ if (hasDynamicResultSets)\r
+ {\r
+ // we can tolerate a count of one less than the\r
+ // expected count, which means the procedure is declared\r
+ // to have dynamic result sets, but the explict signature\r
+ // doesn't have any ResultSet[] types.\r
+ // So accept, and procedure will automatically have 0\r
+ // dynamic results at runtime\r
+ if (count == (signature.length - 1))\r
+ {\r
+ String[] sigs = new String[count];\r
+ System.arraycopy(signatureTypes, 0, sigs, 0, count);\r
+ return sigs;\r
+ }\r
+ }\r
+ throw StandardException.newException(SQLState.SQLJ_SIGNATURE_PARAMETER_COUNT, \r
+ Integer.toString(count),\r
+ Integer.toString(signature.length)); // too few types\r
+ }\r
+\r
+ return signatureTypes;\r
+ }\r
+\r
+ /**\r
+ * Return true if some parameters are null, false otherwise.\r
+ */\r
+ protected boolean someParametersAreNull()\r
+ {\r
+ int count = signature.length;\r
+ \r
+ for ( int ictr = 0; ictr < count; ictr++ )\r
+ {\r
+ if ( signature[ictr] == null )\r
+ {\r
+ return true;\r
+ }\r
+ }\r
+\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Build an array of names of the argument types. These types are biased toward\r
+ * Java objects. That is, if an argument is of SQLType, then we map it to the\r
+ * corresponding Java synonym class (e.g., SQLINT is mapped to 'java.lang.Integer').\r
+ *\r
+ *\r
+ * @return array of type names\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ protected String[] getObjectSignature( )\r
+ throws StandardException\r
+ {\r
+ int count = signature.length;\r
+ String parmTypeNames[] = new String[ count ];\r
+\r
+ for ( int i = 0; i < count; i++ ) { parmTypeNames[i] = getObjectTypeName( signature[ i ] ); }\r
+\r
+ return parmTypeNames;\r
+ }\r
+\r
+ /**\r
+ * Build an array of booleans denoting whether or not a given method\r
+ * parameter is a ?.\r
+ *\r
+ * @return array of booleans denoting wheter or not a given method\r
+ * parameter is a ?.\r
+ */\r
+ protected boolean[] getIsParam()\r
+ {\r
+ if (methodParms == null)\r
+ {\r
+ return new boolean[0];\r
+ }\r
+ \r
+ boolean[] isParam = new boolean[methodParms.length];\r
+\r
+ for (int index = 0; index < methodParms.length; index++)\r
+ {\r
+ if (methodParms[index] instanceof SQLToJavaValueNode)\r
+ {\r
+ SQLToJavaValueNode stjvn = (SQLToJavaValueNode) methodParms[index];\r
+ if (stjvn.value.requiresTypeFromContext())\r
+ {\r
+ isParam[index] = true;\r
+ }\r
+ }\r
+ }\r
+\r
+ return isParam;\r
+ }\r
+\r
+ private String getObjectTypeName( JSQLType jsqlType )\r
+ throws StandardException\r
+ {\r
+ if ( jsqlType != null )\r
+ {\r
+ switch( jsqlType.getCategory() )\r
+ {\r
+ case JSQLType.SQLTYPE: \r
+\r
+ TypeId ctid = mapToTypeID( jsqlType );\r
+\r
+ if ( ctid == null ) { return null; }\r
+ else {\r
+ // DB2 LUW does not support Java object types for SMALLINT, INTEGER, BIGINT, REAL, DOUBLE\r
+ // and these are the only types that can map to a primitive or an object type according\r
+ // to SQL part 13. So always map to the primitive type. We can not use the getPrimitiveSignature()\r
+ // as it (incorrectly but historically always has) maps a DECIMAL to a double. \r
+\r
+ switch (ctid.getJDBCTypeId()) {\r
+ case java.sql.Types.SMALLINT:\r
+ case java.sql.Types.INTEGER:\r
+ case java.sql.Types.BIGINT:\r
+ case java.sql.Types.REAL:\r
+ case java.sql.Types.DOUBLE:\r
+ if (routineInfo != null) {\r
+ TypeCompiler tc = getTypeCompiler(ctid);\r
+ return tc.getCorrespondingPrimitiveTypeName();\r
+ }\r
+ // fall through\r
+ default:\r
+ return ctid.getCorrespondingJavaTypeName();\r
+ }\r
+ }\r
+\r
+ case JSQLType.JAVA_CLASS: return jsqlType.getJavaClassName();\r
+\r
+ case JSQLType.JAVA_PRIMITIVE: return JSQLType.primitiveNames[ jsqlType.getPrimitiveKind() ];\r
+\r
+ default:\r
+\r
+ if (SanityManager.DEBUG)\r
+ { SanityManager.THROWASSERT( "Unknown JSQLType: " + jsqlType ); }\r
+\r
+ }\r
+ }\r
+\r
+ return "";\r
+ }\r
+\r
+ String[] getPrimitiveSignature( boolean castToPrimitiveAsNecessary )\r
+ throws StandardException\r
+ {\r
+ int count = signature.length;\r
+ String[] primParmTypeNames = new String[ count ];\r
+ JSQLType jsqlType;\r
+\r
+ for (int i = 0; i < count; i++)\r
+ {\r
+ jsqlType = signature[ i ];\r
+\r
+ if ( jsqlType == null ) { primParmTypeNames[i] = ""; }\r
+ else\r
+ {\r
+ switch( jsqlType.getCategory() )\r
+ {\r
+ case JSQLType.SQLTYPE:\r
+\r
+ if ((procedurePrimitiveArrayType != null)\r
+ && (i < procedurePrimitiveArrayType.length)\r
+ && (procedurePrimitiveArrayType[i] != null)) {\r
+\r
+ primParmTypeNames[i] = procedurePrimitiveArrayType[i];\r
+\r
+ } else {\r
+\r
+\r
+ TypeId ctid = mapToTypeID( jsqlType );\r
+\r
+ if ((ctid.isNumericTypeId() && !ctid.isDecimalTypeId()) || ctid.isBooleanTypeId())\r
+ {\r
+ TypeCompiler tc = getTypeCompiler(ctid);\r
+ primParmTypeNames[i] = tc.getCorrespondingPrimitiveTypeName();\r
+ if ( castToPrimitiveAsNecessary) { methodParms[i].castToPrimitive(true); }\r
+ }\r
+ else { primParmTypeNames[i] = ctid.getCorrespondingJavaTypeName(); }\r
+ }\r
+\r
+ break;\r
+\r
+ case JSQLType.JAVA_CLASS:\r
+\r
+ primParmTypeNames[i] = jsqlType.getJavaClassName();\r
+ break;\r
+\r
+ case JSQLType.JAVA_PRIMITIVE:\r
+\r
+ primParmTypeNames[i] = JSQLType.primitiveNames[ jsqlType.getPrimitiveKind() ];\r
+ if ( castToPrimitiveAsNecessary) { methodParms[i].castToPrimitive(true); }\r
+ break;\r
+\r
+ default:\r
+\r
+ if (SanityManager.DEBUG)\r
+ { SanityManager.THROWASSERT( "Unknown JSQLType: " + jsqlType ); }\r
+\r
+ } // end switch\r
+\r
+ } // end if\r
+\r
+ } // end for\r
+\r
+ return primParmTypeNames;\r
+ }\r
+\r
+ /**\r
+ * Return the variant type for the underlying expression.\r
+ * The variant type can be:\r
+ * VARIANT - variant within a scan\r
+ * (non-static field access)\r
+ * SCAN_INVARIANT - invariant within a scan\r
+ * (column references from outer tables)\r
+ * QUERY_INVARIANT - invariant within the life of a query\r
+ * (constant expressions)\r
+ *\r
+ * @return The variant type for the underlying expression.\r
+ */\r
+ protected int getOrderableVariantType() throws StandardException\r
+ {\r
+ // beetle 4880. We return the most variant type of the parameters. If no\r
+ // params then query-invariant. This makes more sense, and we can evaluate\r
+ // only once per query (good for performance) because method call could be\r
+ // expensive. And if we push down method qualifier to store, language\r
+ // can pre-evaluate the method call. This avoids letting store evaluate\r
+ // the method while holding page latch, causing deadlock.\r
+\r
+ return getVariantTypeOfParams();\r
+ }\r
+\r
+ private int getVariantTypeOfParams() throws StandardException\r
+ {\r
+ int variance = Qualifier.QUERY_INVARIANT;\r
+\r
+ if (methodParms != null)\r
+ {\r
+ for (int parm = 0; parm < methodParms.length; parm++)\r
+ {\r
+ if (methodParms[parm] != null)\r
+ {\r
+ int paramVariantType =\r
+ methodParms[parm].getOrderableVariantType();\r
+ if (paramVariantType < variance) //return the most variant type\r
+ variance = paramVariantType;\r
+ }\r
+ else\r
+ {\r
+ variance = Qualifier.VARIANT;\r
+ }\r
+ }\r
+ }\r
+\r
+ return variance;\r
+ }\r
+\r
+\r
+ /////////////////////////////////////////////////////////////////////\r
+ //\r
+ // ACCESSORS\r
+ //\r
+ /////////////////////////////////////////////////////////////////////\r
+ /**\r
+ * Get the method parameters.\r
+ * \r
+ * @return The method parameters\r
+ */\r
+ public JavaValueNode[] getMethodParms()\r
+ {\r
+ return methodParms;\r
+ }\r
+\r
+ /**\r
+ * Accept a visitor, and call v.visit()\r
+ * on child nodes as necessary. \r
+ * \r
+ * @param v the visitor\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ public Visitable accept(Visitor v) \r
+ throws StandardException\r
+ {\r
+ Visitable returnNode = v.visit(this);\r
+\r
+ if (v.skipChildren(this))\r
+ {\r
+ return returnNode;\r
+ }\r
+\r
+ for (int parm = 0; \r
+ !v.stopTraversal() && parm < methodParms.length; \r
+ parm++)\r
+ {\r
+ if (methodParms[parm] != null)\r
+ {\r
+ methodParms[parm] = (JavaValueNode)methodParms[parm].accept(v);\r
+ }\r
+ }\r
+\r
+ return returnNode;\r
+ }\r
+}\r