0001: /*
0002:
0003: Derby - Class org.apache.derby.impl.sql.compile.MethodCallNode
0004:
0005: Licensed to the Apache Software Foundation (ASF) under one or more
0006: contributor license agreements. See the NOTICE file distributed with
0007: this work for additional information regarding copyright ownership.
0008: The ASF licenses this file to you under the Apache License, Version 2.0
0009: (the "License"); you may not use this file except in compliance with
0010: the License. You may obtain a copy of the License at
0011:
0012: http://www.apache.org/licenses/LICENSE-2.0
0013:
0014: Unless required by applicable law or agreed to in writing, software
0015: distributed under the License is distributed on an "AS IS" BASIS,
0016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: See the License for the specific language governing permissions and
0018: limitations under the License.
0019:
0020: */
0021:
0022: package org.apache.derby.impl.sql.compile;
0023:
0024: import org.apache.derby.iapi.services.loader.ClassInspector;
0025:
0026: import org.apache.derby.iapi.services.compiler.MethodBuilder;
0027:
0028: import org.apache.derby.iapi.services.sanity.SanityManager;
0029:
0030: import org.apache.derby.iapi.error.StandardException;
0031:
0032: import org.apache.derby.iapi.types.TypeId;
0033: import org.apache.derby.iapi.types.JSQLType;
0034:
0035: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
0036:
0037: import org.apache.derby.iapi.sql.compile.Visitable;
0038: import org.apache.derby.iapi.sql.compile.Visitor;
0039: import org.apache.derby.iapi.sql.compile.C_NodeTypes;
0040: import org.apache.derby.iapi.sql.compile.CompilerContext;
0041:
0042: import org.apache.derby.iapi.types.DataTypeDescriptor;
0043:
0044: import org.apache.derby.iapi.sql.compile.TypeCompiler;
0045: import org.apache.derby.catalog.TypeDescriptor;
0046:
0047: import org.apache.derby.iapi.reference.SQLState;
0048: import org.apache.derby.iapi.reference.JDBC30Translation;
0049:
0050: import org.apache.derby.iapi.store.access.Qualifier;
0051:
0052: import org.apache.derby.iapi.util.JBitSet;
0053:
0054: import org.apache.derby.impl.sql.compile.ExpressionClassBuilder;
0055: import org.apache.derby.catalog.types.RoutineAliasInfo;
0056:
0057: import java.lang.reflect.Modifier;
0058: import java.lang.reflect.Member;
0059:
0060: import java.util.StringTokenizer;
0061: import java.util.Vector;
0062:
0063: /**
0064: * A MethodCallNode represents a Java method call. Method calls can be done
0065: * through DML (as expressions) or through the CALL statement.
0066: *
0067: * @author Jeff Lichtman
0068: */
0069:
0070: abstract class MethodCallNode extends JavaValueNode {
0071: /*
0072: ** Name of the method.
0073: */
0074: String methodName;
0075:
0076: /** The name of the class containing the method. May not be known until bindExpression() has been called.
0077: * @see #bindExpression
0078: * @see #getJavaClassName()
0079: */
0080: String javaClassName;
0081:
0082: /**
0083: For a procedure or function call
0084: */
0085: RoutineAliasInfo routineInfo;
0086:
0087: /**
0088: True if this is an internal call, just used to set up a generated method call.
0089: */
0090: boolean internalCall;
0091:
0092: /**
0093: For resolution of procedure INOUT/OUT parameters to the primitive
0094: form, such as int[]. May be null.
0095: */
0096: private String[] procedurePrimitiveArrayType;
0097:
0098: // bound signature of arguments, stated in universal types (JSQLType)
0099: protected JSQLType[] signature;
0100:
0101: /*
0102: ** Parameters to the method, if any. No elements if no parameters.
0103: */
0104: protected JavaValueNode[] methodParms;
0105:
0106: /* The method call */
0107: protected Member method;
0108:
0109: protected String actualMethodReturnType;
0110:
0111: /**
0112: * Gets the signature of JSQLTypes needed to propagate a work unit from
0113: * target to source.
0114: *
0115: * @return the JSQLType signature
0116: */
0117: public JSQLType[] getSignature() {
0118: return signature;
0119: }
0120:
0121: /**
0122: The parameter types for the resolved method.
0123: */
0124: String[] methodParameterTypes;
0125:
0126: /**
0127: * Initializer for a MethodCallNode
0128: *
0129: * @param methodName The name of the method to call
0130: */
0131: public void init(Object methodName) {
0132: this .methodName = (String) methodName;
0133: }
0134:
0135: public String getMethodName() {
0136: return methodName;
0137: }
0138:
0139: /**
0140: * @return the name of the class that contains the method, null if not known. It may not be known
0141: * until this node has been bound.
0142: */
0143: public String getJavaClassName() {
0144: return javaClassName;
0145: }
0146:
0147: /**
0148: * Set the clause that this node appears in.
0149: *
0150: * @param clause The clause that this node appears in.
0151: */
0152: public void setClause(int clause) {
0153: super .setClause(clause);
0154: if (methodParms != null) {
0155: for (int parm = 0; parm < methodParms.length; parm++) {
0156: if (methodParms[parm] != null) {
0157: methodParms[parm].setClause(clause);
0158: }
0159: }
0160: }
0161: }
0162:
0163: /**
0164: * Add the parameter list.
0165: * (This flavor is useful when transforming a non-static method call node
0166: * to a static method call node.)
0167: *
0168: * @param methodParms JavaValueNode[]
0169: */
0170:
0171: public void addParms(JavaValueNode[] methodParms) {
0172: this .methodParms = methodParms;
0173: }
0174:
0175: /**
0176: * Add the parameter list
0177: *
0178: * @param parameterList A Vector of the parameters
0179: *
0180: * @exception StandardException Thrown on error
0181: */
0182: public void addParms(Vector parameterList) throws StandardException {
0183: methodParms = new JavaValueNode[parameterList.size()];
0184:
0185: int plSize = parameterList.size();
0186: for (int index = 0; index < plSize; index++) {
0187: QueryTreeNode qt;
0188:
0189: qt = (QueryTreeNode) parameterList.elementAt(index);
0190:
0191: /*
0192: ** Since we need the parameter to be in Java domain format, put a
0193: ** SQLToJavaValueNode on top of the parameter node if it is a
0194: ** SQLValueNode. But if the parameter is already in Java domain
0195: ** format, then we don't need to do anything.
0196: */
0197: if (!(qt instanceof JavaValueNode)) {
0198: qt = (SQLToJavaValueNode) getNodeFactory().getNode(
0199: C_NodeTypes.SQL_TO_JAVA_VALUE_NODE, qt,
0200: getContextManager());
0201: }
0202:
0203: methodParms[index] = (JavaValueNode) qt;
0204: }
0205: }
0206:
0207: /**
0208: * Prints the sub-nodes of this object. See QueryTreeNode.java for
0209: * how tree printing is supposed to work.
0210: *
0211: * @param depth The depth of this node in the tree
0212: */
0213:
0214: public void printSubNodes(int depth) {
0215: if (SanityManager.DEBUG) {
0216: int parm;
0217:
0218: super .printSubNodes(depth);
0219: if (methodParms != null) {
0220: for (parm = 0; parm < methodParms.length; parm++) {
0221: if (methodParms[parm] != null) {
0222: printLabel(depth, "methodParms[" + parm + "] :");
0223: methodParms[parm].treePrint(depth + 1);
0224: }
0225: }
0226: }
0227: }
0228: }
0229:
0230: /**
0231: * Convert this object to a String. See comments in QueryTreeNode.java
0232: * for how this should be done for tree printing.
0233: *
0234: * @return This object as a String
0235: */
0236:
0237: public String toString() {
0238: if (SanityManager.DEBUG) {
0239: return "methodName: "
0240: + (methodName != null ? methodName : "null") + "\n"
0241: + super .toString();
0242: } else {
0243: return "";
0244: }
0245: }
0246:
0247: /**
0248: * Bind this expression. This means binding the sub-expressions,
0249: * as well as figuring out what the return type is for this expression.
0250: *
0251: * @param fromList The FROM list for the query this
0252: * expression is in, for binding columns.
0253: * @param subqueryList The subquery list being built as we find SubqueryNodes
0254: * @param aggregateVector The aggregate vector being built as we find AggregateNodes
0255: *
0256: * @exception StandardException Thrown on error
0257: */
0258:
0259: final void bindParameters(FromList fromList,
0260: SubqueryList subqueryList, Vector aggregateVector)
0261: throws StandardException {
0262: /* Bind the parameters */
0263: if (methodParms != null) {
0264: int count = methodParms.length;
0265:
0266: // with a procedure call the signature
0267: // is preformed in StaticMethodCall from
0268: // the procedures signature.
0269: if (signature == null)
0270: signature = new JSQLType[count];
0271:
0272: for (int parm = 0; parm < count; parm++) {
0273: if (methodParms[parm] != null) {
0274: methodParms[parm] = methodParms[parm]
0275: .bindExpression(fromList, subqueryList,
0276: aggregateVector);
0277:
0278: if (routineInfo == null)
0279: signature[parm] = methodParms[parm]
0280: .getJSQLType();
0281:
0282: // prohibit LOB columns/types
0283: if (signature[parm] != null) {
0284: String type = signature[parm].getSQLType()
0285: .getTypeId().getSQLTypeName();
0286: if (type.equals("BLOB") || type.equals("CLOB")
0287: || type.equals("NCLOB")) {
0288: throw StandardException
0289: .newException(SQLState.LOB_AS_METHOD_ARGUMENT_OR_RECEIVER);
0290: }
0291: }
0292: }
0293: }
0294: }
0295: }
0296:
0297: /**
0298: * Return whether or not all of the parameters to this node are
0299: * QUERY_INVARIANT or CONSTANT. This is useful for VTIs - a VTI is a candidate
0300: * for materialization if all of its parameters are QUERY_INVARIANT or CONSTANT
0301: *
0302: * @return Whether or not all of the parameters to this node are QUERY_INVARIANT or CONSTANT
0303: * @exception StandardException thrown on error
0304: */
0305: protected boolean areParametersQueryInvariant()
0306: throws StandardException {
0307: return (getVariantTypeOfParams() == Qualifier.QUERY_INVARIANT);
0308: }
0309:
0310: /**
0311: * Build parameters for error message and throw the exception when there
0312: * is no matching signature found.
0313: *
0314: * @param receiverTypeName Type name for receiver
0315: * @param parmTypeNames Type names for parameters as object types
0316: * @param primParmTypeNames Type names for parameters as primitive types
0317: *
0318: * @exception StandardException Thrown on error
0319: */
0320: void throwNoMethodFound(String receiverTypeName,
0321: String[] parmTypeNames, String[] primParmTypeNames)
0322: throws StandardException {
0323: /* Put the parameter type names into a single string */
0324: StringBuffer parmTypes = new StringBuffer();
0325: for (int i = 0; i < parmTypeNames.length; i++) {
0326: if (i != 0)
0327: parmTypes.append(", ");
0328: /* RESOLVE - shouldn't be using hard coded strings for output */
0329: parmTypes
0330: .append((parmTypeNames[i].length() != 0 ? parmTypeNames[i]
0331: : "UNTYPED"));
0332: if ((primParmTypeNames != null)
0333: && !primParmTypeNames[i].equals(parmTypeNames[i])) // has primitive
0334: parmTypes.append("(" + primParmTypeNames[i] + ")");
0335: }
0336:
0337: throw StandardException.newException(
0338: SQLState.LANG_NO_METHOD_FOUND, receiverTypeName,
0339: methodName, parmTypes);
0340: }
0341:
0342: /**
0343: * Preprocess an expression tree. We do a number of transformations
0344: * here (including subqueries, IN lists, LIKE and BETWEEN) plus
0345: * subquery flattening.
0346: * NOTE: This is done before the outer ResultSetNode is preprocessed.
0347: *
0348: * @param numTables Number of tables in the DML Statement
0349: * @param outerFromList FromList from outer query block
0350: * @param outerSubqueryList SubqueryList from outer query block
0351: * @param outerPredicateList PredicateList from outer query block
0352: *
0353: * @exception StandardException Thrown on error
0354: */
0355: public void preprocess(int numTables, FromList outerFromList,
0356: SubqueryList outerSubqueryList,
0357: PredicateList outerPredicateList) throws StandardException {
0358: int parm;
0359:
0360: /* Preprocess the parameters */
0361: if (methodParms != null) {
0362: for (parm = 0; parm < methodParms.length; parm++) {
0363: if (methodParms[parm] != null) {
0364: methodParms[parm].preprocess(numTables,
0365: outerFromList, outerSubqueryList,
0366: outerPredicateList);
0367: }
0368: }
0369: }
0370: }
0371:
0372: /**
0373: * Categorize this predicate. Initially, this means
0374: * building a bit map of the referenced tables for each predicate.
0375: * If the source of this ColumnReference (at the next underlying level)
0376: * is not a ColumnReference or a VirtualColumnNode then this predicate
0377: * will not be pushed down.
0378: *
0379: * For example, in:
0380: * select * from (select 1 from s) a (x) where x = 1
0381: * we will not push down x = 1.
0382: * NOTE: It would be easy to handle the case of a constant, but if the
0383: * inner SELECT returns an arbitrary expression, then we would have to copy
0384: * that tree into the pushed predicate, and that tree could contain
0385: * subqueries and method calls.
0386: * RESOLVE - revisit this issue once we have views.
0387: *
0388: * @param referencedTabs JBitSet with bit map of referenced FromTables
0389: * @param simplePredsOnly Whether or not to consider method
0390: * calls, field references and conditional nodes
0391: * when building bit map
0392: *
0393: * @return boolean Whether or not source.expression is a ColumnReference
0394: * or a VirtualColumnNode.
0395: * @exception StandardException Thrown on error
0396: */
0397: public boolean categorize(JBitSet referencedTabs,
0398: boolean simplePredsOnly) throws StandardException {
0399: /* We stop here when only considering simple predicates
0400: * as we don't consider method calls when looking
0401: * for null invariant predicates.
0402: */
0403: if (simplePredsOnly) {
0404: return false;
0405: }
0406:
0407: boolean pushable = true;
0408: int param;
0409:
0410: if (methodParms != null) {
0411: for (param = 0; param < methodParms.length; param++) {
0412: if (methodParms[param] != null) {
0413: pushable = methodParms[param].categorize(
0414: referencedTabs, simplePredsOnly)
0415: && pushable;
0416: }
0417: }
0418: }
0419:
0420: /* We need to push down method call. Then the predicate can be used for start/stop
0421: * key for index scan. The fact that method call's cost is not predictable and can
0422: * be expensive doesn't mean we shouldn't push it down. Beetle 4826.
0423: */
0424: return pushable;
0425: }
0426:
0427: /**
0428: * Remap all ColumnReferences in this tree to be clones of the
0429: * underlying expression.
0430: *
0431: * @return JavaValueNode The remapped expression tree.
0432: *
0433: * @exception StandardException Thrown on error
0434: */
0435: public JavaValueNode remapColumnReferencesToExpressions()
0436: throws StandardException {
0437: int param;
0438:
0439: if (methodParms != null) {
0440: for (param = 0; param < methodParms.length; param++) {
0441: if (methodParms[param] != null) {
0442: methodParms[param] = methodParms[param]
0443: .remapColumnReferencesToExpressions();
0444: }
0445: }
0446: }
0447: return this ;
0448: }
0449:
0450: /**
0451: * Generate the parameters to the given method call
0452: *
0453: * @param acb The ExpressionClassBuilder for the class we're generating
0454: * @param mb the method the expression will go into
0455: *
0456: * @return Count of arguments to the method.
0457: *
0458: * @exception StandardException Thrown on error
0459: */
0460:
0461: public int generateParameters(ExpressionClassBuilder acb,
0462: MethodBuilder mb) throws StandardException {
0463: int param;
0464:
0465: String[] expectedTypes = methodParameterTypes;
0466:
0467: ClassInspector classInspector = getClassFactory()
0468: .getClassInspector();
0469:
0470: /* Generate the code for each user parameter, generating the appropriate
0471: * cast when the passed type needs to get widened to the expected type.
0472: */
0473: for (param = 0; param < methodParms.length; param++) {
0474: generateOneParameter(acb, mb, param);
0475:
0476: // type from the SQL-J expression
0477: String argumentType = getParameterTypeName(methodParms[param]);
0478:
0479: // type of the method
0480: String parameterType = expectedTypes[param];
0481:
0482: if (!parameterType.equals(argumentType)) {
0483: // since we reached here through method resolution
0484: // casts are only required for primitive types.
0485: // In any other case the expression type must be assignable
0486: // to the parameter type.
0487: if (classInspector.primitiveType(parameterType)) {
0488: mb.cast(parameterType);
0489: } else {
0490:
0491: // for a prodcedure
0492: if (routineInfo != null) {
0493: continue; // probably should be only for INOUT/OUT parameters.
0494: }
0495:
0496: if (SanityManager.DEBUG) {
0497: SanityManager
0498: .ASSERT(
0499: classInspector.assignableTo(
0500: argumentType,
0501: parameterType),
0502: "Argument type "
0503: + argumentType
0504: + " is not assignable to parameter "
0505: + parameterType);
0506: }
0507:
0508: /*
0509: ** Set the parameter type in case the argument type is narrower
0510: ** than the parameter type.
0511: */
0512: mb.upCast(parameterType);
0513:
0514: }
0515: }
0516:
0517: }
0518:
0519: return methodParms.length;
0520: }
0521:
0522: static public String getParameterTypeName(JavaValueNode param)
0523: throws StandardException {
0524: String argumentType;
0525:
0526: // RESOLVE - shouldn't this logic be inside JavaValueNode ??
0527: // I.e. once the value is primitive then its java type name is its
0528: // primitive type name.
0529: if (param.isPrimitiveType()) {
0530: argumentType = param.getPrimitiveTypeName();
0531: } else {
0532: argumentType = param.getJavaTypeName();
0533: }
0534:
0535: return argumentType;
0536: }
0537:
0538: /**
0539: * Generate one parameter to the given method call. This method is overriden by
0540: * RepStaticMethodCallNode.
0541: *
0542: * @param acb The ExpressionClassBuilder for the class we're generating
0543: * @param mb the method the expression will go into
0544: * @param parameterNumber Identifies which parameter to generate. 0 based.
0545: *
0546: * @exception StandardException Thrown on error
0547: */
0548:
0549: public void generateOneParameter(ExpressionClassBuilder acb,
0550: MethodBuilder mb, int parameterNumber)
0551: throws StandardException {
0552: methodParms[parameterNumber].generateExpression(acb, mb);
0553: }
0554:
0555: /**
0556: * Set the appropriate type information for a null passed as a parameter.
0557: * This method is called after method resolution, when a signature was
0558: * successfully matched.
0559: *
0560: * @param parmTypeNames String[] with the java type names for the parameters
0561: * as declared by the method
0562: *
0563: * @exception StandardException Thrown on error
0564: */
0565: public void setNullParameterInfo(String[] parmTypeNames)
0566: throws StandardException {
0567: for (int i = 0; i < methodParms.length; i++) {
0568: /* null parameters are represented by a java type name of "" */
0569: if (methodParms[i].getJavaTypeName().equals("")) {
0570: /* Set the type information in the null constant node */
0571: DataTypeDescriptor dts = DataTypeDescriptor
0572: .getSQLDataTypeDescriptor(parmTypeNames[i]);
0573: ((SQLToJavaValueNode) methodParms[i]).value
0574: .setType(dts);
0575:
0576: /* Set the correct java type name */
0577: methodParms[i].setJavaTypeName(parmTypeNames[i]);
0578: signature[i] = methodParms[i].getJSQLType();
0579: }
0580: }
0581: }
0582:
0583: protected void resolveMethodCall(String javaClassName,
0584: boolean staticMethod) throws StandardException {
0585: // only allow direct method calls through routines and internal SQL.
0586: if (routineInfo == null && !internalCall) {
0587: // See if we are being executed in an internal context
0588: if ((getCompilerContext().getReliability() & CompilerContext.INTERNAL_SQL_ILLEGAL) != 0) {
0589: throw StandardException.newException(
0590: SQLState.LANG_SYNTAX_ERROR, javaClassName
0591: + (staticMethod ? "::" : ".")
0592: + methodName);
0593: }
0594: }
0595:
0596: int count = signature.length;
0597:
0598: ClassInspector classInspector = getClassFactory()
0599: .getClassInspector();
0600:
0601: String[] parmTypeNames;
0602: String[] primParmTypeNames = null;
0603: boolean[] isParam = getIsParam();
0604:
0605: boolean hasDynamicResultSets = (routineInfo != null)
0606: && (count != 0) && (count != methodParms.length);
0607:
0608: /*
0609: ** Find the matching method that is public.
0610: */
0611:
0612: int signatureOffset = methodName.indexOf('(');
0613:
0614: // support Java signatures by checking if the method name contains a '('
0615: if (signatureOffset != -1) {
0616: parmTypeNames = parseValidateSignature(methodName,
0617: signatureOffset, hasDynamicResultSets);
0618: methodName = methodName.substring(0, signatureOffset);
0619:
0620: // If the signature is specified then Derby resolves to exactly
0621: // that method. Setting this flag to false disables the method
0622: // resolution from automatically optionally repeating the last
0623: // parameter as needed.
0624: hasDynamicResultSets = false;
0625:
0626: } else {
0627: parmTypeNames = getObjectSignature();
0628: }
0629: try {
0630: method = classInspector.findPublicMethod(javaClassName,
0631: methodName, parmTypeNames, null, isParam,
0632: staticMethod, hasDynamicResultSets);
0633:
0634: // DB2 LUW does not support Java object types for SMALLINT, INTEGER, BIGINT, REAL, DOUBLE
0635: // and these are the only types that can map to a primitive or an object type according
0636: // to SQL part 13. So we never have a second chance match.
0637: // Also if the DDL specified a signature, then no alternate resolution
0638: if (signatureOffset == -1 && routineInfo == null) {
0639:
0640: /* If no match, then retry with combinations of object and
0641: * primitive types.
0642: */
0643: if (method == null) {
0644: primParmTypeNames = getPrimitiveSignature(false);
0645:
0646: method = classInspector.findPublicMethod(
0647: javaClassName, methodName, parmTypeNames,
0648: primParmTypeNames, isParam, staticMethod,
0649: hasDynamicResultSets);
0650: }
0651: }
0652: } catch (ClassNotFoundException e) {
0653: /*
0654: ** If one of the classes couldn't be found, just act like the
0655: ** method couldn't be found. The error lists all the class names,
0656: ** which should give the user enough info to diagnose the problem.
0657: */
0658: method = null;
0659: }
0660: /* Throw exception if no matching signature found */
0661: if (method == null) {
0662: throwNoMethodFound(javaClassName, parmTypeNames,
0663: primParmTypeNames);
0664: }
0665:
0666: String typeName = classInspector.getType(method);
0667: actualMethodReturnType = typeName;
0668:
0669: if (routineInfo == null) {
0670:
0671: /* void methods are only okay for CALL Statements */
0672: if (typeName.equals("void")) {
0673: if (!forCallStatement)
0674: throw StandardException
0675: .newException(SQLState.LANG_VOID_METHOD_CALL);
0676: }
0677: } else {
0678: String promoteName = null;
0679: TypeDescriptor returnType = routineInfo.getReturnType();
0680: String requiredType;
0681: if (returnType == null) {
0682: // must have a void method for a procedure call.
0683: requiredType = "void";
0684: } else {
0685:
0686: // DB2 LUW does not support Java object types for SMALLINT, INTEGER, BIGINT, REAL, DOUBLE
0687: // and these are the only types that can map to a primitive or an object type according
0688: // to SQL part 13. So always map to the primitive type. We can not use the getPrimitiveSignature()
0689: // as it (incorrectly but historically always has) maps a DECIMAL to a double.
0690:
0691: TypeId returnTypeId = TypeId
0692: .getBuiltInTypeId(returnType.getJDBCTypeId());
0693: switch (returnType.getJDBCTypeId()) {
0694: case java.sql.Types.SMALLINT:
0695: case java.sql.Types.INTEGER:
0696: case java.sql.Types.BIGINT:
0697: case java.sql.Types.REAL:
0698: case java.sql.Types.DOUBLE:
0699: TypeCompiler tc = getTypeCompiler(returnTypeId);
0700: requiredType = tc
0701: .getCorrespondingPrimitiveTypeName();
0702: if (!routineInfo.calledOnNullInput()
0703: && routineInfo.getParameterCount() != 0) {
0704: promoteName = returnTypeId
0705: .getCorrespondingJavaTypeName();
0706: }
0707:
0708: break;
0709: default:
0710: requiredType = returnTypeId
0711: .getCorrespondingJavaTypeName();
0712: break;
0713: }
0714:
0715: }
0716:
0717: if (!requiredType.equals(typeName)) {
0718: throwNoMethodFound(requiredType + " " + javaClassName,
0719: parmTypeNames, primParmTypeNames);
0720: }
0721:
0722: // for a returns null on null input with a primitive
0723: // type we need to promote to an object so we can return null.
0724: if (promoteName != null)
0725: typeName = promoteName;
0726: }
0727: setJavaTypeName(typeName);
0728:
0729: methodParameterTypes = classInspector.getParameterTypes(method);
0730:
0731: for (int i = 0; i < methodParameterTypes.length; i++) {
0732: String methodParameter = methodParameterTypes[i];
0733:
0734: if (routineInfo != null) {
0735: if (i < routineInfo.getParameterCount()) {
0736: int parameterMode = routineInfo.getParameterModes()[i];
0737:
0738: switch (parameterMode) {
0739: case JDBC30Translation.PARAMETER_MODE_IN:
0740: break;
0741: case JDBC30Translation.PARAMETER_MODE_IN_OUT:
0742: // we need to see if the type of the array is
0743: // primitive, not the array itself.
0744: methodParameter = methodParameter.substring(0,
0745: methodParameter.length() - 2);
0746: break;
0747:
0748: case JDBC30Translation.PARAMETER_MODE_OUT:
0749: // value is not obtained *from* parameter.
0750: continue;
0751: }
0752: }
0753: }
0754:
0755: if (ClassInspector.primitiveType(methodParameter))
0756: methodParms[i].castToPrimitive(true);
0757: }
0758:
0759: /* Set type info for any null parameters */
0760: if (someParametersAreNull()) {
0761: setNullParameterInfo(methodParameterTypes);
0762: }
0763:
0764: /* bug 4450 - if the callable statement is ? = call form, generate the metadata
0765: infor for the return parameter. We don't really need that info in order to
0766: execute the callable statement. But with jdbc3.0, this information should be
0767: made available for return parameter through ParameterMetaData class.
0768: Parser sets a flag in compilercontext if ? = call. If the flag is set,
0769: we generate the metadata info for the return parameter and reset the flag
0770: in the compilercontext for future call statements*/
0771: DataTypeDescriptor dts = DataTypeDescriptor
0772: .getSQLDataTypeDescriptor(typeName);
0773: if (getCompilerContext().getReturnParameterFlag()) {
0774: getCompilerContext().getParameterTypes()[0] = dts;
0775: }
0776: }
0777:
0778: /**
0779: * Parse the user supplied signature for a method and validate
0780: * it, need to match the number of parameters passed in and match
0781: * the valid types for the parameter.
0782: * @param offset Character offset of first paren
0783: * @param hasDynamicResultSets Can ResultSet[] parameters be specified.
0784: * @return The valid array of types for resolution.
0785: * @throws StandardException
0786: */
0787: private String[] parseValidateSignature(String externalName,
0788: int offset, boolean hasDynamicResultSets)
0789: throws StandardException {
0790: int siglen = externalName.length();
0791:
0792: // Ensure the opening paren is not the last
0793: // character and that the last character is a close paren
0794: if (((offset + 1) == siglen)
0795: || (externalName.charAt(siglen - 1) != ')'))
0796: throw StandardException
0797: .newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid
0798:
0799: StringTokenizer st = new StringTokenizer(externalName
0800: .substring(offset + 1, siglen - 1), ",", true);
0801:
0802: String[] signatureTypes = new String[signature.length];
0803: int count;
0804: boolean seenClass = false;
0805: for (count = 0; st.hasMoreTokens();) {
0806: String type = st.nextToken().trim();
0807:
0808: // check sequence is <class><comma>class> etc.
0809: if (",".equals(type)) {
0810: if (!seenClass)
0811: throw StandardException
0812: .newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid
0813: seenClass = false;
0814: continue;
0815: } else {
0816: if (type.length() == 0)
0817: throw StandardException
0818: .newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid
0819: seenClass = true;
0820: count++;
0821: }
0822:
0823: if (count > signature.length) {
0824: if (hasDynamicResultSets) {
0825: // Allow any number of dynamic result set holders
0826: // but they must match the exact type.
0827: String rsType = signature[signature.length - 1]
0828: .getSQLType().getTypeId()
0829: .getCorrespondingJavaTypeName();
0830:
0831: if (!type.equals(rsType))
0832: throw StandardException.newException(
0833: SQLState.LANG_DATA_TYPE_GET_MISMATCH,
0834: type, rsType);
0835:
0836: if (signatureTypes.length == signature.length) {
0837: // expand once
0838: String[] sigs = new String[st.countTokens()];
0839: System.arraycopy(signatureTypes, 0, sigs, 0,
0840: signatureTypes.length);
0841: signatureTypes = sigs;
0842: }
0843:
0844: signatureTypes[count - 1] = type;
0845: continue;
0846:
0847: }
0848: throw StandardException.newException(
0849: SQLState.SQLJ_SIGNATURE_PARAMETER_COUNT,
0850: Integer.toString(count), Integer
0851: .toString(signature.length)); // too many types
0852: }
0853:
0854: TypeId paramTypeId = signature[count - 1].getSQLType()
0855: .getTypeId();
0856:
0857: // Does it match the object name
0858: if (type.equals(paramTypeId.getCorrespondingJavaTypeName())) {
0859: signatureTypes[count - 1] = type;
0860: continue;
0861: }
0862:
0863: // how about the primitive name
0864: if ((paramTypeId.isNumericTypeId() && !paramTypeId
0865: .isDecimalTypeId())
0866: || paramTypeId.isBooleanTypeId()) {
0867: TypeCompiler tc = getTypeCompiler(paramTypeId);
0868: if (type.equals(tc.getCorrespondingPrimitiveTypeName())) {
0869: signatureTypes[count - 1] = type;
0870: continue;
0871: }
0872: }
0873: throw StandardException.newException(
0874: SQLState.LANG_DATA_TYPE_GET_MISMATCH, type,
0875: paramTypeId.getSQLTypeName()); // type conversion error
0876: }
0877:
0878: // Did signature end with trailing comma?
0879: if (count != 0 && !seenClass)
0880: throw StandardException
0881: .newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid
0882:
0883: if (count < signatureTypes.length) {
0884: if (hasDynamicResultSets) {
0885: // we can tolerate a count of one less than the
0886: // expected count, which means the procedure is declared
0887: // to have dynamic result sets, but the explict signature
0888: // doesn't have any ResultSet[] types.
0889: // So accept, and procedure will automatically have 0
0890: // dynamic results at runtime
0891: if (count == (signature.length - 1)) {
0892: String[] sigs = new String[count];
0893: System.arraycopy(signatureTypes, 0, sigs, 0, count);
0894: return sigs;
0895: }
0896: }
0897: throw StandardException.newException(
0898: SQLState.SQLJ_SIGNATURE_PARAMETER_COUNT, Integer
0899: .toString(count), Integer
0900: .toString(signature.length)); // too few types
0901: }
0902:
0903: return signatureTypes;
0904: }
0905:
0906: /**
0907: * Return true if some parameters are null, false otherwise.
0908: */
0909: protected boolean someParametersAreNull() {
0910: int count = signature.length;
0911:
0912: for (int ictr = 0; ictr < count; ictr++) {
0913: if (signature[ictr] == null) {
0914: return true;
0915: }
0916: }
0917:
0918: return false;
0919: }
0920:
0921: /**
0922: * Build an array of names of the argument types. These types are biased toward
0923: * Java objects. That is, if an argument is of SQLType, then we map it to the
0924: * corresponding Java synonym class (e.g., SQLINT is mapped to 'java.lang.Integer').
0925: *
0926: *
0927: * @return array of type names
0928: *
0929: * @exception StandardException Thrown on error
0930: */
0931: protected String[] getObjectSignature() throws StandardException {
0932: int count = signature.length;
0933: String parmTypeNames[] = new String[count];
0934:
0935: for (int i = 0; i < count; i++) {
0936: parmTypeNames[i] = getObjectTypeName(signature[i]);
0937: }
0938:
0939: return parmTypeNames;
0940: }
0941:
0942: /**
0943: * Build an array of booleans denoting whether or not a given method
0944: * parameter is a ?.
0945: *
0946: * @return array of booleans denoting wheter or not a given method
0947: * parameter is a ?.
0948: */
0949: protected boolean[] getIsParam() {
0950: if (methodParms == null) {
0951: return new boolean[0];
0952: }
0953:
0954: boolean[] isParam = new boolean[methodParms.length];
0955:
0956: for (int index = 0; index < methodParms.length; index++) {
0957: if (methodParms[index] instanceof SQLToJavaValueNode) {
0958: SQLToJavaValueNode stjvn = (SQLToJavaValueNode) methodParms[index];
0959: if (stjvn.value.requiresTypeFromContext()) {
0960: isParam[index] = true;
0961: }
0962: }
0963: }
0964:
0965: return isParam;
0966: }
0967:
0968: private String getObjectTypeName(JSQLType jsqlType)
0969: throws StandardException {
0970: if (jsqlType != null) {
0971: switch (jsqlType.getCategory()) {
0972: case JSQLType.SQLTYPE:
0973:
0974: TypeId ctid = mapToTypeID(jsqlType);
0975:
0976: if (ctid == null) {
0977: return null;
0978: } else {
0979: // DB2 LUW does not support Java object types for SMALLINT, INTEGER, BIGINT, REAL, DOUBLE
0980: // and these are the only types that can map to a primitive or an object type according
0981: // to SQL part 13. So always map to the primitive type. We can not use the getPrimitiveSignature()
0982: // as it (incorrectly but historically always has) maps a DECIMAL to a double.
0983:
0984: switch (ctid.getJDBCTypeId()) {
0985: case java.sql.Types.SMALLINT:
0986: case java.sql.Types.INTEGER:
0987: case java.sql.Types.BIGINT:
0988: case java.sql.Types.REAL:
0989: case java.sql.Types.DOUBLE:
0990: if (routineInfo != null) {
0991: TypeCompiler tc = getTypeCompiler(ctid);
0992: return tc
0993: .getCorrespondingPrimitiveTypeName();
0994: }
0995: // fall through
0996: default:
0997: return ctid.getCorrespondingJavaTypeName();
0998: }
0999: }
1000:
1001: case JSQLType.JAVA_CLASS:
1002: return jsqlType.getJavaClassName();
1003:
1004: case JSQLType.JAVA_PRIMITIVE:
1005: return JSQLType.primitiveNames[jsqlType
1006: .getPrimitiveKind()];
1007:
1008: default:
1009:
1010: if (SanityManager.DEBUG) {
1011: SanityManager.THROWASSERT("Unknown JSQLType: "
1012: + jsqlType);
1013: }
1014:
1015: }
1016: }
1017:
1018: return "";
1019: }
1020:
1021: String[] getPrimitiveSignature(boolean castToPrimitiveAsNecessary)
1022: throws StandardException {
1023: int count = signature.length;
1024: String[] primParmTypeNames = new String[count];
1025: JSQLType jsqlType;
1026:
1027: for (int i = 0; i < count; i++) {
1028: jsqlType = signature[i];
1029:
1030: if (jsqlType == null) {
1031: primParmTypeNames[i] = "";
1032: } else {
1033: switch (jsqlType.getCategory()) {
1034: case JSQLType.SQLTYPE:
1035:
1036: if ((procedurePrimitiveArrayType != null)
1037: && (i < procedurePrimitiveArrayType.length)
1038: && (procedurePrimitiveArrayType[i] != null)) {
1039:
1040: primParmTypeNames[i] = procedurePrimitiveArrayType[i];
1041:
1042: } else {
1043:
1044: TypeId ctid = mapToTypeID(jsqlType);
1045:
1046: if ((ctid.isNumericTypeId() && !ctid
1047: .isDecimalTypeId())
1048: || ctid.isBooleanTypeId()) {
1049: TypeCompiler tc = getTypeCompiler(ctid);
1050: primParmTypeNames[i] = tc
1051: .getCorrespondingPrimitiveTypeName();
1052: if (castToPrimitiveAsNecessary) {
1053: methodParms[i].castToPrimitive(true);
1054: }
1055: } else {
1056: primParmTypeNames[i] = ctid
1057: .getCorrespondingJavaTypeName();
1058: }
1059: }
1060:
1061: break;
1062:
1063: case JSQLType.JAVA_CLASS:
1064:
1065: primParmTypeNames[i] = jsqlType.getJavaClassName();
1066: break;
1067:
1068: case JSQLType.JAVA_PRIMITIVE:
1069:
1070: primParmTypeNames[i] = JSQLType.primitiveNames[jsqlType
1071: .getPrimitiveKind()];
1072: if (castToPrimitiveAsNecessary) {
1073: methodParms[i].castToPrimitive(true);
1074: }
1075: break;
1076:
1077: default:
1078:
1079: if (SanityManager.DEBUG) {
1080: SanityManager.THROWASSERT("Unknown JSQLType: "
1081: + jsqlType);
1082: }
1083:
1084: } // end switch
1085:
1086: } // end if
1087:
1088: } // end for
1089:
1090: return primParmTypeNames;
1091: }
1092:
1093: /**
1094: * Return the variant type for the underlying expression.
1095: * The variant type can be:
1096: * VARIANT - variant within a scan
1097: * (non-static field access)
1098: * SCAN_INVARIANT - invariant within a scan
1099: * (column references from outer tables)
1100: * QUERY_INVARIANT - invariant within the life of a query
1101: * (constant expressions)
1102: *
1103: * @return The variant type for the underlying expression.
1104: */
1105: protected int getOrderableVariantType() throws StandardException {
1106: // beetle 4880. We return the most variant type of the parameters. If no
1107: // params then query-invariant. This makes more sense, and we can evaluate
1108: // only once per query (good for performance) because method call could be
1109: // expensive. And if we push down method qualifier to store, language
1110: // can pre-evaluate the method call. This avoids letting store evaluate
1111: // the method while holding page latch, causing deadlock.
1112:
1113: return getVariantTypeOfParams();
1114: }
1115:
1116: private int getVariantTypeOfParams() throws StandardException {
1117: int variance = Qualifier.QUERY_INVARIANT;
1118:
1119: if (methodParms != null) {
1120: for (int parm = 0; parm < methodParms.length; parm++) {
1121: if (methodParms[parm] != null) {
1122: int paramVariantType = methodParms[parm]
1123: .getOrderableVariantType();
1124: if (paramVariantType < variance) //return the most variant type
1125: variance = paramVariantType;
1126: } else {
1127: variance = Qualifier.VARIANT;
1128: }
1129: }
1130: }
1131:
1132: return variance;
1133: }
1134:
1135: /////////////////////////////////////////////////////////////////////
1136: //
1137: // ACCESSORS
1138: //
1139: /////////////////////////////////////////////////////////////////////
1140: /**
1141: * Get the method parameters.
1142: *
1143: * @return The method parameters
1144: */
1145: public JavaValueNode[] getMethodParms() {
1146: return methodParms;
1147: }
1148:
1149: /**
1150: * Accept a visitor, and call v.visit()
1151: * on child nodes as necessary.
1152: *
1153: * @param v the visitor
1154: *
1155: * @exception StandardException on error
1156: */
1157: public Visitable accept(Visitor v) throws StandardException {
1158: Visitable returnNode = v.visit(this );
1159:
1160: if (v.skipChildren(this )) {
1161: return returnNode;
1162: }
1163:
1164: for (int parm = 0; !v.stopTraversal()
1165: && parm < methodParms.length; parm++) {
1166: if (methodParms[parm] != null) {
1167: methodParms[parm] = (JavaValueNode) methodParms[parm]
1168: .accept(v);
1169: }
1170: }
1171:
1172: return returnNode;
1173: }
1174: }
|