--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.StaticMethodCallNode\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.compiler.MethodBuilder;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.sql.compile.CompilerContext;\r
+import org.apache.derby.iapi.sql.compile.TypeCompiler;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+import org.apache.derby.iapi.types.JSQLType;\r
+import org.apache.derby.iapi.types.DataTypeDescriptor;\r
+import org.apache.derby.iapi.types.StringDataValue;\r
+import org.apache.derby.iapi.types.TypeId;\r
+\r
+import org.apache.derby.iapi.sql.dictionary.AliasDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;\r
+\r
+import org.apache.derby.iapi.reference.ClassName;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.reference.JDBC30Translation;\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.impl.sql.compile.ExpressionClassBuilder;\r
+import org.apache.derby.iapi.services.loader.ClassInspector;\r
+import org.apache.derby.iapi.services.compiler.LocalField;\r
+\r
+import org.apache.derby.iapi.util.JBitSet;\r
+import org.apache.derby.iapi.services.classfile.VMOpcode;\r
+\r
+import org.apache.derby.iapi.sql.conn.Authorizer;\r
+\r
+import org.apache.derby.catalog.AliasInfo;\r
+import org.apache.derby.catalog.TypeDescriptor;\r
+import org.apache.derby.catalog.types.RoutineAliasInfo;\r
+import org.apache.derby.impl.sql.compile.ActivationClassBuilder;\r
+\r
+import org.apache.derby.catalog.UUID;\r
+\r
+import java.util.Vector;\r
+import java.lang.reflect.Modifier;\r
+\r
+/**\r
+ * A StaticMethodCallNode represents a static method call from a Class\r
+ * (as opposed to from an Object).\r
+\r
+ For a procedure the call requires that the arguments be ? parameters.\r
+ The parameter is *logically* passed into the method call a number of different ways.\r
+\r
+ <P>\r
+ For a application call like CALL MYPROC(?) the logically Java method call is\r
+ (in psuedo Java/SQL code) (examples with CHAR(10) parameter)\r
+ <BR>\r
+ Fixed length IN parameters - com.acme.MyProcedureMethod(?)\r
+ <BR>\r
+ Variable length IN parameters - com.acme.MyProcedureMethod(CAST (? AS CHAR(10))\r
+ <BR>\r
+ Fixed length INOUT parameter -\r
+ String[] holder = new String[] {?}; com.acme.MyProcedureMethod(holder); ? = holder[0]\r
+ <BR>\r
+ Variable length INOUT parameter -\r
+ String[] holder = new String[] {CAST (? AS CHAR(10)}; com.acme.MyProcedureMethod(holder); ? = CAST (holder[0] AS CHAR(10))\r
+\r
+ <BR>\r
+ Fixed length OUT parameter -\r
+ String[] holder = new String[1]; com.acme.MyProcedureMethod(holder); ? = holder[0]\r
+\r
+ <BR>\r
+ Variable length INOUT parameter -\r
+ String[] holder = new String[1]; com.acme.MyProcedureMethod(holder); ? = CAST (holder[0] AS CHAR(10))\r
+\r
+\r
+ <P>\r
+ For static method calls there is no pre-definition of an IN or INOUT parameter, so a call to CallableStatement.registerOutParameter()\r
+ makes the parameter an INOUT parameter, provided:\r
+ - the parameter is passed directly to the method call (no casts or expressions).\r
+ - the method's parameter type is a Java array type.\r
+\r
+ Since this is a dynmaic decision we compile in code to take both paths, based upon a boolean isINOUT which is dervied from the\r
+ ParameterValueSet. Code is logically (only single parameter String[] shown here). Note, no casts can exist here.\r
+\r
+ boolean isINOUT = getParameterValueSet().getParameterMode(0) == PARAMETER_IN_OUT;\r
+ if (isINOUT) {\r
+ String[] holder = new String[] {?}; com.acme.MyProcedureMethod(holder); ? = holder[0]\r
+ \r
+ } else {\r
+ com.acme.MyProcedureMethod(?)\r
+ }\r
+\r
+ *\r
+ */\r
+public class StaticMethodCallNode extends MethodCallNode\r
+{\r
+ private TableName procedureName;\r
+\r
+ private LocalField[] outParamArrays;\r
+ private int[] applicationParameterNumbers; \r
+\r
+ private boolean isSystemCode;\r
+ private boolean alreadyBound;\r
+\r
+ /**\r
+ * Generated boolean field to hold the indicator\r
+ * for if any of the parameters to a\r
+ * RETURNS NULL ON NULL INPUT function are NULL.\r
+ * Only set if this node is calling such a function.\r
+ * Set at generation time.\r
+ */\r
+ private LocalField returnsNullOnNullState;\r
+\r
+\r
+ AliasDescriptor ad;\r
+\r
+\r
+ /**\r
+ * Intializer for a NonStaticMethodCallNode\r
+ *\r
+ * @param methodName The name of the method to call\r
+ * @param javaClassName The name of the java class that the static method belongs to.\r
+ */\r
+ public void init(Object methodName, Object javaClassName)\r
+ {\r
+ if (methodName instanceof String)\r
+ init(methodName);\r
+ else {\r
+ procedureName = (TableName) methodName;\r
+ init(procedureName.getTableName());\r
+ }\r
+\r
+ this.javaClassName = (String) javaClassName;\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
+ * @return this or an AggregateNode\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public JavaValueNode bindExpression(\r
+ FromList fromList, SubqueryList subqueryList,\r
+ Vector aggregateVector) \r
+ throws StandardException\r
+ {\r
+ // for a function we can get called recursively\r
+ if (alreadyBound)\r
+ return this;\r
+\r
+\r
+ bindParameters(fromList, subqueryList, aggregateVector);\r
+\r
+ \r
+ /* If javaClassName is null then we assume that the current methodName\r
+ * is an alias and we must go to sysmethods to\r
+ * get the real method and java class names for this alias.\r
+ */\r
+ if (javaClassName == null)\r
+ {\r
+ CompilerContext cc = getCompilerContext();\r
+\r
+ // look for a routine\r
+\r
+ String schemaName = procedureName.getSchemaName();\r
+ \r
+ boolean noSchema = schemaName == null;\r
+\r
+ SchemaDescriptor sd = getSchemaDescriptor(schemaName, schemaName != null);\r
+\r
+ // The field methodName is used by resolveRoutine and\r
+ // is set to the name of the routine (procedureName.getTableName()).\r
+ resolveRoutine(fromList, subqueryList, aggregateVector, sd);\r
+ \r
+ if (ad == null && noSchema && !forCallStatement)\r
+ {\r
+ // Resolve to a built-in SYSFUN function but only\r
+ // if this is a function call and the call\r
+ // was not qualified. E.g. COS(angle). The\r
+ // SYSFUN functions are not in SYSALIASES but\r
+ // an in-memory table, set up in DataDictioanryImpl.\r
+ sd = getSchemaDescriptor("SYSFUN", true);\r
+ \r
+ resolveRoutine(fromList, subqueryList, aggregateVector, sd);\r
+ }\r
+ \r
+\r
+ /* Throw exception if no routine found */\r
+ if (ad == null)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_NO_SUCH_METHOD_ALIAS, procedureName);\r
+ }\r
+ \r
+\r
+\r
+ /* Query is dependent on the AliasDescriptor */\r
+ cc.createDependency(ad);\r
+\r
+\r
+ methodName = ad.getAliasInfo().getMethodName();\r
+ javaClassName = ad.getJavaClassName();\r
+ \r
+ // DERBY-2330 Do not allow a routine to resolve to\r
+ // a Java method that is part of the Derby runtime code base.\r
+ // This is a security measure to stop user-defined routines\r
+ // bypassing security by making calls directly to Derby's\r
+ // internal methods. E.g. open a table's conglomerate\r
+ // directly and read the file, bypassing any authorization.\r
+ // This is a simpler mechanism than analyzing all of\r
+ // Derby's public static methods and ensuring they have\r
+ // no Security holes.\r
+ if (javaClassName.startsWith("org.apache.derby."))\r
+ {\r
+ if (!sd.isSystemSchema())\r
+ throw StandardException.newException(\r
+ SQLState.LANG_TYPE_DOESNT_EXIST2, (Throwable) null,\r
+ javaClassName);\r
+ }\r
+ }\r
+\r
+ verifyClassExist(javaClassName);\r
+\r
+ /* Resolve the method call */\r
+ resolveMethodCall(javaClassName, true);\r
+\r
+\r
+ alreadyBound = true;\r
+ if (isPrivilegeCollectionRequired())\r
+ getCompilerContext().addRequiredRoutinePriv(ad);\r
+\r
+ // If this is a function call with a variable length\r
+ // return type, then we need to push a CAST node.\r
+ if (routineInfo != null)\r
+ {\r
+ if (methodParms != null) \r
+ optimizeDomainValueConversion();\r
+ \r
+ TypeDescriptor returnType = routineInfo.getReturnType();\r
+ if (returnType != null)\r
+ {\r
+ TypeId returnTypeId = TypeId.getBuiltInTypeId(returnType.getJDBCTypeId());\r
+\r
+ if (returnTypeId.variableLength()) {\r
+ // Cast the return using a cast node, but have to go\r
+ // into the SQL domain, and back to the Java domain.\r
+\r
+ DataTypeDescriptor returnValueDtd = new DataTypeDescriptor(\r
+ returnTypeId,\r
+ returnType.getPrecision(),\r
+ returnType.getScale(),\r
+ returnType.isNullable(),\r
+ returnType.getMaximumWidth()\r
+ );\r
+ // DERBY-2972 Match the collation of the RoutineAliasInfo \r
+ returnValueDtd.setCollationType(returnType.getCollationType());\r
+ returnValueDtd.setCollationDerivation(StringDataValue.COLLATION_DERIVATION_IMPLICIT);\r
+ ValueNode returnValueToSQL = (ValueNode) getNodeFactory().getNode(\r
+ C_NodeTypes.JAVA_TO_SQL_VALUE_NODE,\r
+ this, \r
+ getContextManager());\r
+\r
+ ValueNode returnValueCastNode = (ValueNode) getNodeFactory().getNode(\r
+ C_NodeTypes.CAST_NODE,\r
+ returnValueToSQL, \r
+ returnValueDtd,\r
+ getContextManager());\r
+\r
+\r
+ JavaValueNode returnValueToJava = (JavaValueNode) getNodeFactory().getNode(\r
+ C_NodeTypes.SQL_TO_JAVA_VALUE_NODE,\r
+ returnValueCastNode, \r
+ getContextManager());\r
+ returnValueToJava.setCollationType(returnType.getCollationType());\r
+ return returnValueToJava.bindExpression(fromList, subqueryList, aggregateVector);\r
+ }\r
+\r
+ }\r
+ }\r
+\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * If this SQL function has parameters which are SQLToJavaValueNode over\r
+ * JavaToSQLValueNode and the java value node underneath is a SQL function\r
+ * defined with CALLED ON NULL INPUT, then we can get rid of the wrapper\r
+ * nodes over the java value node for such parameters. This is because\r
+ * SQL functions defined with CALLED ON NULL INPUT need access to only\r
+ * java domain values.\r
+ * This can't be done for parameters which are wrappers over SQL function\r
+ * defined with RETURN NULL ON NULL INPUT because such functions need\r
+ * access to both sql domain value and java domain value. - Derby479\r
+ */\r
+ private void optimizeDomainValueConversion() throws StandardException {\r
+ int count = methodParms.length;\r
+ for (int parm = 0; parm < count; parm++)\r
+ {\r
+ if (methodParms[parm] instanceof SQLToJavaValueNode &&\r
+ ((SQLToJavaValueNode)methodParms[parm]).getSQLValueNode() instanceof\r
+ JavaToSQLValueNode)\r
+ {\r
+ //If we are here, then it means that the parameter is\r
+ //SQLToJavaValueNode on top of JavaToSQLValueNode\r
+ JavaValueNode paramIsJavaValueNode =\r
+ ((JavaToSQLValueNode)((SQLToJavaValueNode)methodParms[parm]).getSQLValueNode()).getJavaValueNode();\r
+ if (paramIsJavaValueNode instanceof StaticMethodCallNode)\r
+ {\r
+ //If we are here, then it means that the parameter has\r
+ //a MethodCallNode underneath it.\r
+ StaticMethodCallNode paramIsMethodCallNode = (StaticMethodCallNode)paramIsJavaValueNode;\r
+ //If the MethodCallNode parameter is defined as\r
+ //CALLED ON NULL INPUT, then we can remove the wrappers\r
+ //for the param and just set the parameter to the\r
+ //java value node.\r
+ if (paramIsMethodCallNode.routineInfo != null &&\r
+ paramIsMethodCallNode.routineInfo.calledOnNullInput())\r
+ methodParms[parm] =\r
+ ((JavaToSQLValueNode)((SQLToJavaValueNode)methodParms[parm]).getSQLValueNode()).getJavaValueNode();\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Resolve a routine. Obtain a list of routines from the data dictionary\r
+ * of the correct type (functions or procedures) and name.\r
+ * Pick the best routine from the list. Currently only a single routine\r
+ * with a given type and name is allowed, thus if changes are made to\r
+ * support overloaded routines, careful code inspection and testing will\r
+ * be required.\r
+ * @param fromList\r
+ * @param subqueryList\r
+ * @param aggregateVector\r
+ * @param sd\r
+ * @throws StandardException\r
+ */\r
+ private void resolveRoutine(FromList fromList, SubqueryList subqueryList, Vector aggregateVector, SchemaDescriptor sd) throws StandardException {\r
+ if (sd.getUUID() != null) {\r
+\r
+ java.util.List list = getDataDictionary().getRoutineList(\r
+ sd.getUUID().toString(), methodName,\r
+ forCallStatement ? AliasInfo.ALIAS_NAME_SPACE_PROCEDURE_AS_CHAR : AliasInfo.ALIAS_NAME_SPACE_FUNCTION_AS_CHAR\r
+ );\r
+\r
+ for (int i = list.size() - 1; i >= 0; i--) {\r
+\r
+ AliasDescriptor proc = (AliasDescriptor) list.get(i);\r
+\r
+ RoutineAliasInfo routineInfo = (RoutineAliasInfo) proc.getAliasInfo();\r
+ int parameterCount = routineInfo.getParameterCount();\r
+ if (parameterCount != methodParms.length)\r
+ continue;\r
+\r
+ // pre-form the method signature. If it is a dynamic result set procedure\r
+ // then we need to add in the ResultSet array\r
+\r
+ TypeDescriptor[] parameterTypes = routineInfo.getParameterTypes();\r
+\r
+ int sigParameterCount = parameterCount;\r
+ if (routineInfo.getMaxDynamicResultSets() > 0)\r
+ sigParameterCount++;\r
+\r
+ signature = new JSQLType[sigParameterCount];\r
+ for (int p = 0; p < parameterCount; p++) {\r
+\r
+ // find the declared type.\r
+\r
+ TypeDescriptor td = parameterTypes[p];\r
+\r
+ TypeId typeId = TypeId.getBuiltInTypeId(td.getJDBCTypeId());\r
+\r
+ TypeId parameterTypeId = typeId;\r
+\r
+\r
+ // if it's an OUT or INOUT parameter we need an array.\r
+ int parameterMode = routineInfo.getParameterModes()[p];\r
+\r
+ if (parameterMode != JDBC30Translation.PARAMETER_MODE_IN) {\r
+\r
+ String arrayType;\r
+ switch (typeId.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
+ arrayType = getTypeCompiler(typeId).getCorrespondingPrimitiveTypeName().concat("[]");\r
+ break;\r
+ default:\r
+ arrayType = typeId.getCorrespondingJavaTypeName().concat("[]");\r
+ break;\r
+ }\r
+\r
+ typeId = TypeId.getUserDefinedTypeId(arrayType, false);\r
+ }\r
+\r
+ // this is the type descriptor of the require method parameter\r
+ DataTypeDescriptor methoddtd = new DataTypeDescriptor(\r
+ typeId,\r
+ td.getPrecision(),\r
+ td.getScale(),\r
+ td.isNullable(),\r
+ td.getMaximumWidth()\r
+ );\r
+\r
+ signature[p] = new JSQLType(methoddtd);\r
+\r
+ // check parameter is a ? node for INOUT and OUT parameters.\r
+\r
+ ValueNode sqlParamNode = null;\r
+\r
+ if (methodParms[p] instanceof SQLToJavaValueNode) {\r
+ SQLToJavaValueNode sql2j = (SQLToJavaValueNode) methodParms[p];\r
+ sqlParamNode = sql2j.getSQLValueNode();\r
+ }\r
+ else\r
+ {\r
+ }\r
+\r
+ boolean isParameterMarker = true;\r
+ if ((sqlParamNode == null) || !sqlParamNode.requiresTypeFromContext())\r
+ {\r
+ if (parameterMode != JDBC30Translation.PARAMETER_MODE_IN) {\r
+ \r
+ throw StandardException.newException(SQLState.LANG_DB2_PARAMETER_NEEDS_MARKER,\r
+ RoutineAliasInfo.parameterMode(parameterMode),\r
+ routineInfo.getParameterNames()[p]);\r
+ }\r
+ isParameterMarker = false;\r
+ }\r
+ else\r
+ {\r
+ if (applicationParameterNumbers == null)\r
+ applicationParameterNumbers = new int[parameterCount];\r
+ if (sqlParamNode instanceof UnaryOperatorNode) {\r
+ ParameterNode pn = ((UnaryOperatorNode)sqlParamNode).getParameterOperand();\r
+ applicationParameterNumbers[p] = pn.getParameterNumber();\r
+ } else\r
+ applicationParameterNumbers[p] = ((ParameterNode) sqlParamNode).getParameterNumber();\r
+ }\r
+\r
+ // this is the SQL type of the procedure parameter.\r
+ DataTypeDescriptor paramdtd = new DataTypeDescriptor(\r
+ parameterTypeId,\r
+ td.getPrecision(),\r
+ td.getScale(),\r
+ td.isNullable(),\r
+ td.getMaximumWidth()\r
+ );\r
+\r
+ boolean needCast = false;\r
+ if (!isParameterMarker)\r
+ {\r
+\r
+ // can only be an IN parameter.\r
+ // check that the value can be assigned to the\r
+ // type of the procedure parameter.\r
+ if (sqlParamNode instanceof UntypedNullConstantNode)\r
+ {\r
+ sqlParamNode.setType(paramdtd);\r
+ }\r
+ else\r
+ {\r
+\r
+\r
+ DataTypeDescriptor dts;\r
+ TypeId argumentTypeId;\r
+\r
+ if (sqlParamNode != null)\r
+ {\r
+ // a node from the SQL world\r
+ argumentTypeId = sqlParamNode.getTypeId();\r
+ dts = sqlParamNode.getTypeServices();\r
+ }\r
+ else\r
+ {\r
+ // a node from the Java world\r
+ dts = DataTypeDescriptor.getSQLDataTypeDescriptor(methodParms[p].getJavaTypeName());\r
+ if (dts == null)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_NO_CORRESPONDING_S_Q_L_TYPE, \r
+ methodParms[p].getJavaTypeName());\r
+ }\r
+\r
+ argumentTypeId = dts.getTypeId();\r
+ }\r
+\r
+ if (! getTypeCompiler(parameterTypeId).storable(argumentTypeId, getClassFactory()))\r
+ throw StandardException.newException(SQLState.LANG_NOT_STORABLE, \r
+ parameterTypeId.getSQLTypeName(),\r
+ argumentTypeId.getSQLTypeName() );\r
+\r
+ // if it's not an exact length match then some cast will be needed.\r
+ if (!paramdtd.isExactTypeAndLengthMatch(dts))\r
+ needCast = true;\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // any variable length type will need a cast from the\r
+ // Java world (the ? parameter) to the SQL type. This\r
+ // ensures values like CHAR(10) are passed into the procedure\r
+ // correctly as 10 characters long.\r
+ if (parameterTypeId.variableLength()) {\r
+\r
+ if (parameterMode != JDBC30Translation.PARAMETER_MODE_OUT)\r
+ needCast = true;\r
+ }\r
+ }\r
+ \r
+\r
+ if (needCast)\r
+ {\r
+ // push a cast node to ensure the\r
+ // correct type is passed to the method\r
+ // this gets tacky because before we knew\r
+ // it was a procedure call we ensured all the\r
+ // parameter are JavaNodeTypes. Now we need to\r
+ // push them back to the SQL domain, cast them\r
+ // and then push them back to the Java domain.\r
+\r
+ if (sqlParamNode == null) {\r
+\r
+ sqlParamNode = (ValueNode) getNodeFactory().getNode(\r
+ C_NodeTypes.JAVA_TO_SQL_VALUE_NODE,\r
+ methodParms[p], \r
+ getContextManager());\r
+ }\r
+\r
+ ValueNode castNode = (ValueNode) getNodeFactory().getNode(\r
+ C_NodeTypes.CAST_NODE,\r
+ sqlParamNode, \r
+ paramdtd,\r
+ getContextManager());\r
+\r
+\r
+ methodParms[p] = (JavaValueNode) getNodeFactory().getNode(\r
+ C_NodeTypes.SQL_TO_JAVA_VALUE_NODE,\r
+ castNode, \r
+ getContextManager());\r
+\r
+ methodParms[p] = methodParms[p].bindExpression(fromList, subqueryList, aggregateVector);\r
+ }\r
+\r
+ // only force the type for a ? so that the correct type shows up\r
+ // in parameter meta data\r
+ if (isParameterMarker)\r
+ sqlParamNode.setType(paramdtd);\r
+ }\r
+\r
+ if (sigParameterCount != parameterCount) {\r
+\r
+ TypeId typeId = TypeId.getUserDefinedTypeId("java.sql.ResultSet[]", false);\r
+\r
+ DataTypeDescriptor dtd = new DataTypeDescriptor(\r
+ typeId,\r
+ 0,\r
+ 0,\r
+ false,\r
+ -1\r
+ );\r
+\r
+ signature[parameterCount] = new JSQLType(dtd);\r
+\r
+ }\r
+\r
+ this.routineInfo = routineInfo;\r
+ ad = proc;\r
+\r
+ // If a procedure is in the system schema and defined as executing\r
+ // SQL do we set we are in system code.\r
+ if (sd.isSystemSchema() && (routineInfo.getReturnType() == null) && routineInfo.getSQLAllowed() != RoutineAliasInfo.NO_SQL)\r
+ isSystemCode = true;\r
+\r
+ break;\r
+ }\r
+}\r
+ }\r
+\r
+ /**\r
+ Push extra code to generate the casts within the\r
+ arrays for the parameters passed as arrays.\r
+ */\r
+ public void generateOneParameter(ExpressionClassBuilder acb,\r
+ MethodBuilder mb,\r
+ int parameterNumber )\r
+ throws StandardException\r
+ {\r
+ int parameterMode;\r
+\r
+ SQLToJavaValueNode sql2j = null;\r
+ if (methodParms[parameterNumber] instanceof SQLToJavaValueNode)\r
+ sql2j = (SQLToJavaValueNode) methodParms[parameterNumber];\r
+ \r
+ if (routineInfo != null) {\r
+ parameterMode = routineInfo.getParameterModes()[parameterNumber];\r
+ } else {\r
+ // for a static method call the parameter always starts out as a in parameter, but\r
+ // may be registered as an IN OUT parameter. For a static method argument to be\r
+ // a dynmaically registered out parameter it must be a simple ? parameter\r
+\r
+ parameterMode = JDBC30Translation.PARAMETER_MODE_IN;\r
+\r
+ if (sql2j != null) {\r
+ if (sql2j.getSQLValueNode().requiresTypeFromContext()) {\r
+ ParameterNode pn;\r
+ if (sql2j.getSQLValueNode() instanceof UnaryOperatorNode) \r
+ pn = ((UnaryOperatorNode)sql2j.getSQLValueNode()).getParameterOperand();\r
+ else\r
+ pn = (ParameterNode) (sql2j.getSQLValueNode());\r
+\r
+ // applicationParameterNumbers is only set up for a procedure.\r
+ int applicationParameterNumber = pn.getParameterNumber();\r
+\r
+ String parameterType = methodParameterTypes[parameterNumber];\r
+\r
+ if (parameterType.endsWith("[]")) {\r
+\r
+ // constructor - setting up correct paramter type info\r
+ MethodBuilder constructor = acb.getConstructor();\r
+ acb.pushThisAsActivation(constructor);\r
+ constructor.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "getParameterValueSet", ClassName.ParameterValueSet, 0);\r
+\r
+ constructor.push(applicationParameterNumber);\r
+ constructor.push(JDBC30Translation.PARAMETER_MODE_UNKNOWN);\r
+ constructor.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "setParameterMode", "void", 2);\r
+ constructor.endStatement();\r
+ }\r
+ }\r
+ } \r
+ }\r
+\r
+ switch (parameterMode) {\r
+ case JDBC30Translation.PARAMETER_MODE_IN:\r
+ case JDBC30Translation.PARAMETER_MODE_IN_OUT:\r
+ case JDBC30Translation.PARAMETER_MODE_UNKNOWN:\r
+ if (sql2j != null)\r
+ sql2j.returnsNullOnNullState = returnsNullOnNullState;\r
+ super.generateOneParameter(acb, mb, parameterNumber);\r
+ break;\r
+\r
+ case JDBC30Translation.PARAMETER_MODE_OUT:\r
+ // For an OUT parameter we require nothing to be pushed into the\r
+ // method call from the parameter node.\r
+ break;\r
+ }\r
+\r
+ switch (parameterMode) {\r
+ case JDBC30Translation.PARAMETER_MODE_IN:\r
+ case JDBC30Translation.PARAMETER_MODE_UNKNOWN:\r
+ break;\r
+\r
+ case JDBC30Translation.PARAMETER_MODE_IN_OUT:\r
+ case JDBC30Translation.PARAMETER_MODE_OUT:\r
+ {\r
+ // Create the array used to pass into the method. We create a\r
+ // new array for each call as there is a small chance the\r
+ // application could retain a reference to it and corrupt\r
+ // future calls with the same CallableStatement object.\r
+\r
+ String methodParameterType = methodParameterTypes[parameterNumber];\r
+ String arrayType = methodParameterType.substring(0, methodParameterType.length() - 2);\r
+ LocalField lf = acb.newFieldDeclaration(Modifier.PRIVATE, methodParameterType);\r
+\r
+ if (outParamArrays == null)\r
+ outParamArrays = new LocalField[methodParms.length];\r
+\r
+ outParamArrays[parameterNumber] = lf;\r
+\r
+ mb.pushNewArray(arrayType, 1);\r
+ mb.putField(lf);\r
+\r
+ // set the IN part of the parameter into the INOUT parameter.\r
+ if (parameterMode != JDBC30Translation.PARAMETER_MODE_OUT) {\r
+ mb.swap();\r
+ mb.setArrayElement(0);\r
+ mb.getField(lf);\r
+ }\r
+ break;\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
+ *\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
+\r
+ pushable = pushable && super.categorize(referencedTabs, simplePredsOnly);\r
+\r
+ return pushable;\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 "javaClassName: " +\r
+ (javaClassName != null ? javaClassName : "null") + "\n" +\r
+ super.toString();\r
+ }\r
+ else\r
+ {\r
+ return "";\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Do code generation for this 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
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void generateExpression(ExpressionClassBuilder acb,\r
+ MethodBuilder mb)\r
+ throws StandardException\r
+ {\r
+ if (routineInfo != null) {\r
+\r
+ if (!routineInfo.calledOnNullInput() && routineInfo.getParameterCount() != 0)\r
+ returnsNullOnNullState = acb.newFieldDeclaration(Modifier.PRIVATE, "boolean");\r
+\r
+ }\r
+\r
+ // reset the parameters are null indicator.\r
+ if (returnsNullOnNullState != null) {\r
+ mb.push(false);\r
+ mb.setField(returnsNullOnNullState);\r
+\r
+ // for the call to the generated method below.\r
+ mb.pushThis();\r
+ }\r
+\r
+ int nargs = generateParameters(acb, mb);\r
+\r
+ LocalField functionEntrySQLAllowed = null;\r
+\r
+ if (routineInfo != null) {\r
+\r
+ short sqlAllowed = routineInfo.getSQLAllowed();\r
+\r
+ // Before we set up our authorization level, add a check to see if this\r
+ // method can be called. If the routine is NO SQL or CONTAINS SQL \r
+ // then there is no need for a check. As follows:\r
+ //\r
+ // Current Level = NO_SQL - CALL will be rejected when getting CALL result set\r
+ // Current Level = anything else - calls to procedures defined as NO_SQL and CONTAINS SQL both allowed.\r
+\r
+\r
+ if (sqlAllowed != RoutineAliasInfo.NO_SQL)\r
+ {\r
+ \r
+ int sqlOperation;\r
+ \r
+ if (sqlAllowed == RoutineAliasInfo.READS_SQL_DATA)\r
+ sqlOperation = Authorizer.SQL_SELECT_OP;\r
+ else if (sqlAllowed == RoutineAliasInfo.MODIFIES_SQL_DATA)\r
+ sqlOperation = Authorizer.SQL_WRITE_OP;\r
+ else\r
+ sqlOperation = Authorizer.SQL_ARBITARY_OP;\r
+ \r
+ generateAuthorizeCheck((ActivationClassBuilder) acb, mb, sqlOperation);\r
+ }\r
+\r
+ int statmentContextReferences = isSystemCode ? 2 : 1;\r
+ \r
+ boolean isFunction = routineInfo.getReturnType() != null;\r
+\r
+ if (isFunction)\r
+ statmentContextReferences++;\r
+\r
+\r
+ if (statmentContextReferences != 0) {\r
+ acb.pushThisAsActivation(mb);\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "getLanguageConnectionContext", ClassName.LanguageConnectionContext, 0);\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "getStatementContext", "org.apache.derby.iapi.sql.conn.StatementContext", 0);\r
+\r
+ for (int scc = 1; scc < statmentContextReferences; scc++)\r
+ mb.dup();\r
+ }\r
+\r
+ /**\r
+ Set the statement context to reflect we are running\r
+ System procedures, so that we can execute non-standard SQL.\r
+ */\r
+ if (isSystemCode) {\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "setSystemCode", "void", 0);\r
+ }\r
+\r
+ // for a function we need to fetch the current SQL control\r
+ // so that we can reset it once the function is complete.\r
+ // \r
+ if (isFunction)\r
+ {\r
+ functionEntrySQLAllowed = acb.newFieldDeclaration(Modifier.PRIVATE, "short");\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "getSQLAllowed", "short", 0);\r
+ mb.setField(functionEntrySQLAllowed);\r
+\r
+ }\r
+ \r
+ \r
+ // Set up the statement context to reflect the\r
+ // restricted SQL execution allowed by this routine.\r
+\r
+ mb.push(sqlAllowed);\r
+ mb.push(false);\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "setSQLAllowed", "void", 2);\r
+\r
+ }\r
+\r
+ // add in the ResultSet arrays.\r
+ if (routineInfo != null) {\r
+\r
+ int compiledResultSets = methodParameterTypes.length - methodParms.length;\r
+\r
+ if (compiledResultSets != 0) {\r
+\r
+ // Add a method that indicates the maxium number of dynamic result sets.\r
+ int maxDynamicResults = routineInfo.getMaxDynamicResultSets();\r
+ if (maxDynamicResults > 0) {\r
+ MethodBuilder gdr = acb.getClassBuilder().newMethodBuilder(Modifier.PUBLIC, "int", "getMaxDynamicResults");\r
+ gdr.push(maxDynamicResults);\r
+ gdr.methodReturn();\r
+ gdr.complete();\r
+ }\r
+\r
+ // add a method to return all the dynamic result sets (unordered)\r
+ MethodBuilder gdr = acb.getClassBuilder().newMethodBuilder(Modifier.PUBLIC, "java.sql.ResultSet[][]", "getDynamicResults");\r
+\r
+ MethodBuilder cons = acb.getConstructor();\r
+ // if (procDef.getParameterStyle() == RoutineAliasInfo.PS_JAVA)\r
+ {\r
+ // PARAMETER STYLE JAVA\r
+\r
+ LocalField procedureResultSetsHolder = acb.newFieldDeclaration(Modifier.PRIVATE, "java.sql.ResultSet[][]");\r
+\r
+ // getDynamicResults body\r
+ gdr.getField(procedureResultSetsHolder);\r
+\r
+ // create the holder of all the ResultSet arrays, new java.sql.ResultSet[][compiledResultSets]\r
+ cons.pushNewArray("java.sql.ResultSet[]", compiledResultSets);\r
+ cons.setField(procedureResultSetsHolder);\r
+\r
+\r
+ // arguments for the dynamic result sets\r
+ for (int i = 0; i < compiledResultSets; i++) {\r
+\r
+ mb.pushNewArray("java.sql.ResultSet", 1);\r
+ mb.dup();\r
+\r
+ mb.getField(procedureResultSetsHolder);\r
+ mb.swap();\r
+\r
+ mb.setArrayElement(i);\r
+ }\r
+ } \r
+\r
+ // complete the method that returns the ResultSet[][] to the \r
+ gdr.methodReturn();\r
+ gdr.complete();\r
+\r
+ nargs += compiledResultSets;\r
+ }\r
+\r
+ }\r
+\r
+ String javaReturnType = getJavaTypeName();\r
+\r
+ MethodBuilder mbnc = null;\r
+ MethodBuilder mbcm = mb;\r
+\r
+\r
+ // If any of the parameters are null then\r
+ // do not call the method, just return null.\r
+ if (returnsNullOnNullState != null)\r
+ {\r
+ mbnc = acb.newGeneratedFun(javaReturnType, Modifier.PRIVATE, methodParameterTypes);\r
+\r
+ // add the throws clause for the public static method we are going to call.\r
+ Class[] throwsSet = ((java.lang.reflect.Method) method).getExceptionTypes();\r
+ for (int te = 0; te < throwsSet.length; te++)\r
+ {\r
+ mbnc.addThrownException(throwsSet[te].getName());\r
+ }\r
+\r
+ mbnc.getField(returnsNullOnNullState);\r
+ mbnc.conditionalIf();\r
+\r
+ // set up for a null!!\r
+ // for objects is easy.\r
+ mbnc.pushNull(javaReturnType);\r
+\r
+ mbnc.startElseCode(); \r
+\r
+ if (!actualMethodReturnType.equals(javaReturnType))\r
+ mbnc.pushNewStart(javaReturnType);\r
+\r
+ // fetch all the arguments\r
+ for (int pa = 0; pa < nargs; pa++)\r
+ {\r
+ mbnc.getParameter(pa);\r
+ }\r
+\r
+ mbcm = mbnc;\r
+ }\r
+\r
+ mbcm.callMethod(VMOpcode.INVOKESTATIC, method.getDeclaringClass().getName(), methodName,\r
+ actualMethodReturnType, nargs);\r
+\r
+\r
+ if (returnsNullOnNullState != null)\r
+ {\r
+ if (!actualMethodReturnType.equals(javaReturnType))\r
+ mbnc.pushNewComplete(1);\r
+\r
+ mbnc.completeConditional();\r
+\r
+ mbnc.methodReturn();\r
+ mbnc.complete();\r
+\r
+ // now call the wrapper method\r
+ mb.callMethod(VMOpcode.INVOKEVIRTUAL, acb.getClassBuilder().getFullName(), mbnc.getName(),\r
+ javaReturnType, nargs);\r
+ mbnc = null;\r
+ }\r
+\r
+\r
+ if (routineInfo != null) {\r
+\r
+ // reset the SQL allowed setting that we set upon\r
+ // entry to the method.\r
+ if (functionEntrySQLAllowed != null) {\r
+ acb.pushThisAsActivation(mb);\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "getLanguageConnectionContext", ClassName.LanguageConnectionContext, 0);\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "getStatementContext", "org.apache.derby.iapi.sql.conn.StatementContext", 0);\r
+ mb.getField(functionEntrySQLAllowed);\r
+ mb.push(true); // override as we are ending the control set by this function all.\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "setSQLAllowed", "void", 2);\r
+\r
+ }\r
+\r
+ if (outParamArrays != null) {\r
+\r
+ MethodBuilder constructor = acb.getConstructor();\r
+\r
+ // constructor - setting up correct paramter type info\r
+ acb.pushThisAsActivation(constructor);\r
+ constructor.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "getParameterValueSet", ClassName.ParameterValueSet, 0);\r
+\r
+ // execute - passing out parameters back.\r
+ acb.pushThisAsActivation(mb);\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "getParameterValueSet", ClassName.ParameterValueSet, 0);\r
+\r
+ int[] parameterModes = routineInfo.getParameterModes();\r
+ for (int i = 0; i < outParamArrays.length; i++) {\r
+\r
+ int parameterMode = parameterModes[i];\r
+ if (parameterMode != JDBC30Translation.PARAMETER_MODE_IN) {\r
+\r
+ // must be a parameter if it is INOUT or OUT.\r
+ ValueNode sqlParamNode = ((SQLToJavaValueNode) methodParms[i]).getSQLValueNode();\r
+\r
+\r
+ int applicationParameterNumber = applicationParameterNumbers[i];\r
+\r
+ // Set the correct parameter nodes in the ParameterValueSet at constructor time.\r
+ constructor.dup();\r
+ constructor.push(applicationParameterNumber);\r
+ constructor.push(parameterMode);\r
+ constructor.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "setParameterMode", "void", 2);\r
+\r
+ // Pass the value of the outparameters back to the calling code\r
+ LocalField lf = outParamArrays[i];\r
+\r
+ mb.dup(); \r
+ mb.push(applicationParameterNumber);\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null,\r
+ "getParameter", ClassName.DataValueDescriptor, 1);\r
+\r
+ // see if we need to set the desired length/scale/precision of the type\r
+ DataTypeDescriptor paramdtd = sqlParamNode.getTypeServices();\r
+\r
+ boolean isNumericType = paramdtd.getTypeId().isNumericTypeId();\r
+\r
+ // is the underlying type for the OUT/INOUT parameter primitive.\r
+ boolean isPrimitive = ((java.lang.reflect.Method) method).getParameterTypes()[i].getComponentType().isPrimitive();\r
+\r
+ if (isNumericType) {\r
+ // need to up-cast as the setValue(Number) method only exists on NumberDataValue\r
+\r
+ if (!isPrimitive)\r
+ mb.cast(ClassName.NumberDataValue);\r
+ }\r
+ else if (paramdtd.getTypeId().isBooleanTypeId())\r
+ {\r
+ // need to cast as the setValue(Boolean) method only exists on BooleanDataValue\r
+ if (!isPrimitive)\r
+ mb.cast(ClassName.BooleanDataValue);\r
+ }\r
+\r
+ if (paramdtd.getTypeId().variableLength()) {\r
+ // need another DVD reference for the set width below.\r
+ mb.dup();\r
+ }\r
+\r
+\r
+ mb.getField(lf); // pvs, dvd, array\r
+ mb.getArrayElement(0); // pvs, dvd, value\r
+\r
+ // The value needs to be set thorugh the setValue(Number) method.\r
+ if (isNumericType && !isPrimitive)\r
+ {\r
+ mb.upCast("java.lang.Number");\r
+ }\r
+\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, null, "setValue", "void", 1);\r
+\r
+ if (paramdtd.getTypeId().variableLength()) {\r
+ mb.push(isNumericType ? paramdtd.getPrecision() : paramdtd.getMaximumWidth());\r
+ mb.push(paramdtd.getScale());\r
+ mb.push(isNumericType);\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.VariableSizeDataValue, "setWidth", "void", 3);\r
+ // mb.endStatement();\r
+ }\r
+ }\r
+ }\r
+ constructor.endStatement();\r
+ mb.endStatement();\r
+ }\r
+\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Set default privilege of EXECUTE for this node. \r
+ */\r
+ int getPrivType()\r
+ {\r
+ return Authorizer.EXECUTE_PRIV;\r
+ }\r
+}\r