Source code: org/apache/derby/impl/sql/compile/StaticMethodCallNode.java
1 /*
2
3 Derby - Class org.apache.derby.impl.sql.compile.StaticMethodCallNode
4
5 Copyright 1997, 2004 The Apache Software Foundation or its licensors, as applicable.
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS,
15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 See the License for the specific language governing permissions and
17 limitations under the License.
18
19 */
20
21 package org.apache.derby.impl.sql.compile;
22
23 import org.apache.derby.iapi.services.compiler.MethodBuilder;
24
25 import org.apache.derby.iapi.services.sanity.SanityManager;
26
27 import org.apache.derby.iapi.sql.compile.CompilerContext;
28 import org.apache.derby.iapi.sql.compile.TypeCompiler;
29 import org.apache.derby.iapi.sql.compile.C_NodeTypes;
30 import org.apache.derby.iapi.types.JSQLType;
31 import org.apache.derby.iapi.types.DataTypeDescriptor;
32 import org.apache.derby.iapi.types.TypeId;
33
34 import org.apache.derby.iapi.sql.dictionary.AliasDescriptor;
35 import org.apache.derby.iapi.sql.dictionary.DataDictionary;
36 import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
37
38 import org.apache.derby.iapi.reference.ClassName;
39 import org.apache.derby.iapi.reference.SQLState;
40 import org.apache.derby.iapi.reference.JDBC30Translation;
41 import org.apache.derby.iapi.error.StandardException;
42
43 import org.apache.derby.impl.sql.compile.ExpressionClassBuilder;
44 import org.apache.derby.iapi.services.loader.ClassInspector;
45 import org.apache.derby.iapi.services.compiler.LocalField;
46
47 import org.apache.derby.iapi.util.JBitSet;
48 import org.apache.derby.iapi.services.classfile.VMOpcode;
49
50 import org.apache.derby.iapi.sql.conn.Authorizer;
51
52 import org.apache.derby.catalog.AliasInfo;
53 import org.apache.derby.catalog.TypeDescriptor;
54 import org.apache.derby.catalog.types.RoutineAliasInfo;
55 import org.apache.derby.impl.sql.compile.ActivationClassBuilder;
56
57 import org.apache.derby.catalog.UUID;
58
59 import java.util.Vector;
60 import java.lang.reflect.Modifier;
61
62 /**
63 * A StaticMethodCallNode represents a static method call from a Class
64 * (as opposed to from an Object).
65
66 For a procedure the call requires that the arguments be ? parameters.
67 The parameter is *logically* passed into the method call a number of different ways.
68
69 <P>
70 For a application call like CALL MYPROC(?) the logically Java method call is
71 (in psuedo Java/SQL code) (examples with CHAR(10) parameter)
72 <BR>
73 Fixed length IN parameters - com.acme.MyProcedureMethod(?)
74 <BR>
75 Variable length IN parameters - com.acme.MyProcedureMethod(CAST (? AS CHAR(10))
76 <BR>
77 Fixed length INOUT parameter -
78 String[] holder = new String[] {?}; com.acme.MyProcedureMethod(holder); ? = holder[0]
79 <BR>
80 Variable length INOUT parameter -
81 String[] holder = new String[] {CAST (? AS CHAR(10)}; com.acme.MyProcedureMethod(holder); ? = CAST (holder[0] AS CHAR(10))
82
83 <BR>
84 Fixed length OUT parameter -
85 String[] holder = new String[1]; com.acme.MyProcedureMethod(holder); ? = holder[0]
86
87 <BR>
88 Variable length INOUT parameter -
89 String[] holder = new String[1]; com.acme.MyProcedureMethod(holder); ? = CAST (holder[0] AS CHAR(10))
90
91
92 <P>
93 For static method calls there is no pre-definition of an IN or INOUT parameter, so a call to CallableStatement.registerOutParameter()
94 makes the parameter an INOUT parameter, provided:
95 - the parameter is passed directly to the method call (no casts or expressions).
96 - the method's parameter type is a Java array type.
97
98 Since this is a dynmaic decision we compile in code to take both paths, based upon a boolean isINOUT which is dervied from the
99 ParameterValueSet. Code is logically (only single parameter String[] shown here). Note, no casts can exist here.
100
101 boolean isINOUT = getParameterValueSet().getParameterMode(0) == PARAMETER_IN_OUT;
102 if (isINOUT) {
103 String[] holder = new String[] {?}; com.acme.MyProcedureMethod(holder); ? = holder[0]
104
105 } else {
106 com.acme.MyProcedureMethod(?)
107 }
108
109 *
110 * @author Jerry Brenner
111 */
112 public class StaticMethodCallNode extends MethodCallNode
113 {
114 private TableName procedureName;
115
116 private LocalField[] outParamArrays;
117 private int[] applicationParameterNumbers;
118
119 private boolean isSystemCode;
120 private boolean alreadyBound;
121
122 private LocalField returnsNullOnNullState;
123
124
125 AliasDescriptor ad;
126
127
128 /**
129 * Intializer for a NonStaticMethodCallNode
130 *
131 * @param methodName The name of the method to call
132 * @param javaClassName The name of the java class that the static method belongs to.
133 */
134 public void init(Object methodName, Object javaClassName)
135 {
136 if (methodName instanceof String)
137 init(methodName);
138 else {
139 procedureName = (TableName) methodName;
140 init(procedureName.getTableName());
141 }
142
143 this.javaClassName = (String) javaClassName;
144 }
145
146 /**
147 * Bind this expression. This means binding the sub-expressions,
148 * as well as figuring out what the return type is for this expression.
149 *
150 * @param fromList The FROM list for the query this
151 * expression is in, for binding columns.
152 * @param subqueryList The subquery list being built as we find SubqueryNodes
153 * @param aggregateVector The aggregate vector being built as we find AggregateNodes
154 *
155 * @return this or an AggregateNode
156 *
157 * @exception StandardException Thrown on error
158 */
159
160 public JavaValueNode bindExpression(
161 FromList fromList, SubqueryList subqueryList,
162 Vector aggregateVector)
163 throws StandardException
164 {
165 // for a function we can get called recursively
166 if (alreadyBound)
167 return this;
168
169
170 bindParameters(fromList, subqueryList, aggregateVector);
171
172
173 /* If javaClassName is null then we assume that the current methodName
174 * is an alias and we must go to sysmethods to
175 * get the real method and java class names for this alias.
176 */
177 if (javaClassName == null)
178 {
179 CompilerContext cc = getCompilerContext();
180
181 // look for a routine
182 if (ad == null) {
183
184 String schemaName = procedureName != null ?
185 procedureName.getSchemaName() : null;
186
187 SchemaDescriptor sd = getSchemaDescriptor(schemaName, schemaName != null);
188
189
190 if (sd.getUUID() != null) {
191
192 java.util.List list = getDataDictionary().getRoutineList(
193 sd.getUUID().toString(), methodName,
194 forCallStatement ? AliasInfo.ALIAS_NAME_SPACE_PROCEDURE_AS_CHAR : AliasInfo.ALIAS_NAME_SPACE_FUNCTION_AS_CHAR
195 );
196
197 for (int i = list.size() - 1; i >= 0; i--) {
198
199 AliasDescriptor proc = (AliasDescriptor) list.get(i);
200
201 RoutineAliasInfo routineInfo = (RoutineAliasInfo) proc.getAliasInfo();
202 int parameterCount = routineInfo.getParameterCount();
203 if (parameterCount != methodParms.length)
204 continue;
205
206 // pre-form the method signature. If it is a dynamic result set procedure
207 // then we need to add in the ResultSet array
208
209 TypeDescriptor[] parameterTypes = routineInfo.getParameterTypes();
210
211 int sigParameterCount = parameterCount;
212 if (routineInfo.getMaxDynamicResultSets() > 0)
213 sigParameterCount++;
214
215 signature = new JSQLType[sigParameterCount];
216 for (int p = 0; p < parameterCount; p++) {
217
218 // find the declared type.
219
220 TypeDescriptor td = parameterTypes[p];
221
222 TypeId typeId = TypeId.getBuiltInTypeId(td.getJDBCTypeId());
223
224 TypeId parameterTypeId = typeId;
225
226
227 // if it's an OUT or INOUT parameter we need an array.
228 int parameterMode = routineInfo.getParameterModes()[p];
229
230 if (parameterMode != JDBC30Translation.PARAMETER_MODE_IN) {
231
232 String arrayType;
233 switch (typeId.getJDBCTypeId()) {
234 case java.sql.Types.SMALLINT:
235 case java.sql.Types.INTEGER:
236 case java.sql.Types.BIGINT:
237 case java.sql.Types.REAL:
238 case java.sql.Types.DOUBLE:
239 arrayType = getTypeCompiler(typeId).getCorrespondingPrimitiveTypeName().concat("[]");
240 break;
241 default:
242 arrayType = typeId.getCorrespondingJavaTypeName().concat("[]");
243 break;
244 }
245
246 typeId = TypeId.getUserDefinedTypeId(arrayType, false);
247 }
248
249 // this is the type descriptor of the require method parameter
250 DataTypeDescriptor methoddtd = new DataTypeDescriptor(
251 typeId,
252 td.getPrecision(),
253 td.getScale(),
254 td.isNullable(),
255 td.getMaximumWidth()
256 );
257
258 signature[p] = new JSQLType(methoddtd);
259
260 // check parameter is a ? node for INOUT and OUT parameters.
261
262 ValueNode sqlParamNode = null;
263
264 if (methodParms[p] instanceof SQLToJavaValueNode) {
265 SQLToJavaValueNode sql2j = (SQLToJavaValueNode) methodParms[p];
266 sqlParamNode = sql2j.getSQLValueNode();
267 }
268 else
269 {
270 }
271
272 boolean isParameterMarker = true;
273 if ((sqlParamNode == null) || !sqlParamNode.isParameterNode())
274 {
275 if (parameterMode != JDBC30Translation.PARAMETER_MODE_IN) {
276
277 throw StandardException.newException(SQLState.LANG_DB2_PARAMETER_NEEDS_MARKER,
278 RoutineAliasInfo.parameterMode(parameterMode),
279 routineInfo.getParameterNames()[p]);
280 }
281 isParameterMarker = false;
282 }
283 else
284 {
285 if (applicationParameterNumbers == null)
286 applicationParameterNumbers = new int[parameterCount];
287 applicationParameterNumbers[p] = ((ParameterNode) sqlParamNode).getParameterNumber();
288 }
289
290 // this is the SQL type of the procedure parameter.
291 DataTypeDescriptor paramdtd = new DataTypeDescriptor(
292 parameterTypeId,
293 td.getPrecision(),
294 td.getScale(),
295 td.isNullable(),
296 td.getMaximumWidth()
297 );
298
299 boolean needCast = false;
300 if (!isParameterMarker)
301 {
302
303 // can only be an IN parameter.
304 // check that the value can be assigned to the
305 // type of the procedure parameter.
306 if (sqlParamNode instanceof UntypedNullConstantNode)
307 {
308 sqlParamNode.setDescriptor(paramdtd);
309 }
310 else
311 {
312
313
314 DataTypeDescriptor dts;
315 TypeId argumentTypeId;
316
317 if (sqlParamNode != null)
318 {
319 // a node from the SQL world
320 argumentTypeId = sqlParamNode.getTypeId();
321 dts = sqlParamNode.getTypeServices();
322 }
323 else
324 {
325 // a node from the Java world
326 dts = DataTypeDescriptor.getSQLDataTypeDescriptor(methodParms[p].getJavaTypeName());
327 if (dts == null)
328 {
329 throw StandardException.newException(SQLState.LANG_NO_CORRESPONDING_S_Q_L_TYPE,
330 methodParms[p].getJavaTypeName());
331 }
332
333 argumentTypeId = dts.getTypeId();
334 }
335
336 if (! getTypeCompiler(parameterTypeId).storable(argumentTypeId, getClassFactory()))
337 throw StandardException.newException(SQLState.LANG_NOT_STORABLE,
338 parameterTypeId.getSQLTypeName(),
339 argumentTypeId.getSQLTypeName() );
340
341 // if it's not an exact length match then some cast will be needed.
342 if (!paramdtd.isExactTypeAndLengthMatch(dts))
343 needCast = true;
344 }
345 }
346 else
347 {
348 // any variable length type will need a cast from the
349 // Java world (the ? parameter) to the SQL type. This
350 // ensures values like CHAR(10) are passed into the procedure
351 // correctly as 10 characters long.
352 if (parameterTypeId.variableLength()) {
353
354 if (parameterMode != JDBC30Translation.PARAMETER_MODE_OUT)
355 needCast = true;
356 }
357 }
358
359
360 if (needCast)
361 {
362 // push a cast node to ensure the
363 // correct type is passed to the method
364 // this gets tacky because before we knew
365 // it was a procedure call we ensured all the
366 // parameter are JavaNodeTypes. Now we need to
367 // push them back to the SQL domain, cast them
368 // and then push them back to the Java domain.
369
370 if (sqlParamNode == null) {
371
372 sqlParamNode = (ValueNode) getNodeFactory().getNode(
373 C_NodeTypes.JAVA_TO_SQL_VALUE_NODE,
374 methodParms[p],
375 getContextManager());
376 }
377
378 ValueNode castNode = (ValueNode) getNodeFactory().getNode(
379 C_NodeTypes.CAST_NODE,
380 sqlParamNode,
381 paramdtd,
382 getContextManager());
383
384
385 methodParms[p] = (JavaValueNode) getNodeFactory().getNode(
386 C_NodeTypes.SQL_TO_JAVA_VALUE_NODE,
387 castNode,
388 getContextManager());
389
390 methodParms[p] = methodParms[p].bindExpression(fromList, subqueryList, aggregateVector);
391 }
392
393 // only force the type for a ? so that the correct type shows up
394 // in parameter meta data
395 if (isParameterMarker)
396 sqlParamNode.setDescriptor(paramdtd);
397 }
398
399 if (sigParameterCount != parameterCount) {
400
401 TypeId typeId = TypeId.getUserDefinedTypeId("java.sql.ResultSet[]", false);
402
403 DataTypeDescriptor dtd = new DataTypeDescriptor(
404 typeId,
405 0,
406 0,
407 false,
408 -1
409 );
410
411 signature[parameterCount] = new JSQLType(dtd);
412
413 }
414
415 this.routineInfo = routineInfo;
416 ad = proc;
417
418 // If a procedure is in the system schema and defined as executing
419 // SQL do we set we are in system code.
420 if (sd.isSystemSchema() && (routineInfo.getReturnType() == null) && routineInfo.getSQLAllowed() != RoutineAliasInfo.NO_SQL)
421 isSystemCode = true;
422
423 break;
424 }
425 }
426
427 }
428
429 /* Throw exception if no alias found */
430 if (ad == null)
431 {
432 Object errName;
433 if (procedureName == null)
434 errName = methodName;
435 else
436 errName = procedureName;
437
438 throw StandardException.newException(SQLState.LANG_NO_SUCH_METHOD_ALIAS, errName);
439 }
440
441
442
443 /* Query is dependent on the AliasDescriptor */
444 cc.createDependency(ad);
445
446
447 methodName = ad.getAliasInfo().getMethodName();
448 javaClassName = ad.getJavaClassName();
449 }
450
451
452 javaClassName = verifyClassExist(javaClassName, true);
453
454 /* Resolve the method call */
455 resolveMethodCall(javaClassName, true);
456
457
458 alreadyBound = true;
459
460 // If this is a function call with a variable length
461 // return type, then we need to push a CAST node.
462 if (routineInfo != null)
463 {
464 TypeDescriptor returnType = routineInfo.getReturnType();
465 if (returnType != null)
466 {
467 TypeId returnTypeId = TypeId.getBuiltInTypeId(returnType.getJDBCTypeId());
468
469 if (returnTypeId.variableLength()) {
470 // Cast the return using a cast node, but have to go
471 // into the SQL domain, and back to the Java domain.
472
473 DataTypeDescriptor returnValueDtd = new DataTypeDescriptor(
474 returnTypeId,
475 returnType.getPrecision(),
476 returnType.getScale(),
477 returnType.isNullable(),
478 returnType.getMaximumWidth()
479 );
480
481
482 ValueNode returnValueToSQL = (ValueNode) getNodeFactory().getNode(
483 C_NodeTypes.JAVA_TO_SQL_VALUE_NODE,
484 this,
485 getContextManager());
486
487 ValueNode returnValueCastNode = (ValueNode) getNodeFactory().getNode(
488 C_NodeTypes.CAST_NODE,
489 returnValueToSQL,
490 returnValueDtd,
491 getContextManager());
492
493
494 JavaValueNode returnValueToJava = (JavaValueNode) getNodeFactory().getNode(
495 C_NodeTypes.SQL_TO_JAVA_VALUE_NODE,
496 returnValueCastNode,
497 getContextManager());
498
499 return returnValueToJava.bindExpression(fromList, subqueryList, aggregateVector);
500 }
501
502 }
503 }
504
505 return this;
506 }
507
508 /**
509 Push extra code to generate the casts within the
510 arrays for the parameters passed as arrays.
511 */
512 public void generateOneParameter(ExpressionClassBuilder acb,
513 MethodBuilder mb,
514 int parameterNumber )
515 throws StandardException
516 {
517 int parameterMode;
518
519
520 SQLToJavaValueNode sql2j = null;
521 if (methodParms[parameterNumber] instanceof SQLToJavaValueNode)
522 sql2j = (SQLToJavaValueNode) methodParms[parameterNumber];
523
524 if (routineInfo != null) {
525 parameterMode = routineInfo.getParameterModes()[parameterNumber];
526 } else {
527 // for a static method call the parameter always starts out as a in parameter, but
528 // may be registered as an IN OUT parameter. For a static method argument to be
529 // a dynmaically registered out parameter it must be a simple ? parameter
530
531 parameterMode = JDBC30Translation.PARAMETER_MODE_IN;
532
533 if (sql2j != null) {
534 if (sql2j.getSQLValueNode().isParameterNode()) {
535
536 // applicationParameterNumbers is only set up for a procedure.
537 int applicationParameterNumber = ((ParameterNode) (sql2j.getSQLValueNode())).getParameterNumber();
538
539 String parameterType = methodParameterTypes[parameterNumber];
540
541 if (parameterType.endsWith("[]")) {
542
543 // constructor - setting up correct paramter type info
544 MethodBuilder constructor = acb.getConstructor();
545 acb.pushThisAsActivation(constructor);
546 constructor.callMethod(VMOpcode.INVOKEINTERFACE, null,
547 "getParameterValueSet", ClassName.ParameterValueSet, 0);
548
549 constructor.push(applicationParameterNumber);
550 constructor.push(JDBC30Translation.PARAMETER_MODE_UNKNOWN);
551 constructor.callMethod(VMOpcode.INVOKEINTERFACE, null,
552 "setParameterMode", "void", 2);
553 constructor.endStatement();
554 }
555 }
556 }
557 }
558
559 switch (parameterMode) {
560 case JDBC30Translation.PARAMETER_MODE_IN:
561 case JDBC30Translation.PARAMETER_MODE_IN_OUT:
562 case JDBC30Translation.PARAMETER_MODE_UNKNOWN:
563 if (sql2j != null)
564 sql2j.returnsNullOnNullState = returnsNullOnNullState;
565 super.generateOneParameter(acb, mb, parameterNumber);
566 break;
567
568 case JDBC30Translation.PARAMETER_MODE_OUT:
569 // For an OUT parameter we require nothing to be pushed into the
570 // method call from the parameter node.
571 break;
572 }
573
574 switch (parameterMode) {
575 case JDBC30Translation.PARAMETER_MODE_IN:
576 case JDBC30Translation.PARAMETER_MODE_UNKNOWN:
577 break;
578
579 case JDBC30Translation.PARAMETER_MODE_IN_OUT:
580 case JDBC30Translation.PARAMETER_MODE_OUT:
581 {
582 // Create the array used to pass into the method. We create a
583 // new array for each call as there is a small chance the
584 // application could retain a reference to it and corrupt
585 // future calls with the same CallableStatement object.
586
587 String methodParameterType = methodParameterTypes[parameterNumber];
588 String arrayType = methodParameterType.substring(0, methodParameterType.length() - 2);
589 LocalField lf = acb.newFieldDeclaration(Modifier.PRIVATE, methodParameterType);
590
591 if (outParamArrays == null)
592 outParamArrays = new LocalField[methodParms.length];
593
594 outParamArrays[parameterNumber] = lf;
595
596 mb.pushNewArray(arrayType, 1);
597 mb.putField(lf);
598
599 // set the IN part of the parameter into the INOUT parameter.
600 if (parameterMode != JDBC30Translation.PARAMETER_MODE_OUT) {
601 mb.swap();
602 mb.setArrayElement(0);
603 mb.getField(lf);
604 }
605 break;
606 }
607 }
608
609 }
610
611 /**
612 * Categorize this predicate. Initially, this means
613 * building a bit map of the referenced tables for each predicate.
614 * If the source of this ColumnReference (at the next underlying level)
615 * is not a ColumnReference or a VirtualColumnNode then this predicate
616 * will not be pushed down.
617 *
618 * For example, in:
619 * select * from (select 1 from s) a (x) where x = 1
620 * we will not push down x = 1.
621 * NOTE: It would be easy to handle the case of a constant, but if the
622 * inner SELECT returns an arbitrary expression, then we would have to copy
623 * that tree into the pushed predicate, and that tree could contain
624 * subqueries and method calls.
625 * RESOLVE - revisit this issue once we have views.
626 *
627 * @param referencedTabs JBitSet with bit map of referenced FromTables
628 * @param simplePredsOnly Whether or not to consider method
629 * calls, field references and conditional nodes
630 * when building bit map
631 *
632 * @return boolean Whether or not source.expression is a ColumnReference
633 * or a VirtualColumnNode.
634 *
635 * @exception StandardException Thrown on error
636 */
637 public boolean categorize(JBitSet referencedTabs, boolean simplePredsOnly)
638 throws StandardException
639 {
640 /* We stop here when only considering simple predicates
641 * as we don't consider method calls when looking
642 * for null invariant predicates.
643 */
644 if (simplePredsOnly)
645 {
646 return false;
647 }
648
649 boolean pushable = true;
650
651 pushable = pushable && super.categorize(referencedTabs, simplePredsOnly);
652
653 return pushable;
654 }
655
656 /**
657 * Convert this object to a String. See comments in QueryTreeNode.java
658 * for how this should be done for tree printing.
659 *
660 * @return This object as a String
661 */
662
663 public String toString()
664 {
665 if (SanityManager.DEBUG)
666 {
667 return "javaClassName: " +
668 (javaClassName != null ? javaClassName : "null") + "\n" +
669 super.toString();
670 }
671 else
672 {
673 return "";
674 }
675 }
676
677 /**
678 * Do code generation for this method call
679 *
680 * @param acb The ExpressionClassBuilder for the class we're generating
681 * @param mb The method the expression will go into
682 *
683 *
684 * @exception StandardException Thrown on error
685 */
686
687 public void generateExpression(ExpressionClassBuilder acb,
688 MethodBuilder mb)
689 throws StandardException
690 {
691 if (routineInfo != null) {
692
693 if (!routineInfo.calledOnNullInput() && routineInfo.getParameterCount() != 0)
694 returnsNullOnNullState = acb.newFieldDeclaration(Modifier.PRIVATE, "boolean");
695
696 }
697
698 // reset the parameters are null indicator.
699 if (returnsNullOnNullState != null) {
700 mb.push(false);
701 mb.setField(returnsNullOnNullState);
702
703 // for the call to the generated method below.
704 mb.pushThis();
705 }
706
707 int nargs = generateParameters(acb, mb);
708
709 LocalField functionEntrySQLAllowed = null;
710
711 if (routineInfo != null) {
712
713 short sqlAllowed = routineInfo.getSQLAllowed();
714
715 // Before we set up our authorization level, add a check to see if this
716 // method can be called. If the routine is NO SQL or CONTAINS SQL
717 // then there is no need for a check. As follows:
718 //
719 // Current Level = NO_SQL - CALL will be rejected when getting CALL result set
720 // Current Level = anything else - calls to procedures defined as NO_SQL and CONTAINS SQL both allowed.
721
722
723 if (sqlAllowed != RoutineAliasInfo.NO_SQL)
724 {
725
726 int sqlOperation;
727
728 if (sqlAllowed == RoutineAliasInfo.READS_SQL_DATA)
729 sqlOperation = Authorizer.SQL_SELECT_OP;
730 else if (sqlAllowed == RoutineAliasInfo.MODIFIES_SQL_DATA)
731 sqlOperation = Authorizer.SQL_WRITE_OP;
732 else
733 sqlOperation = Authorizer.SQL_ARBITARY_OP;
734
735 generateAuthorizeCheck((ActivationClassBuilder) acb, mb, sqlOperation);
736 }
737
738 int statmentContextReferences = isSystemCode ? 2 : 1;
739
740 boolean isFunction = routineInfo.getReturnType() != null;
741
742 if (isFunction)
743 statmentContextReferences++;
744
745
746 if (statmentContextReferences != 0) {
747 acb.pushThisAsActivation(mb);
748 mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
749 "getLanguageConnectionContext", ClassName.LanguageConnectionContext, 0);
750 mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
751 "getStatementContext", "org.apache.derby.iapi.sql.conn.StatementContext", 0);
752
753 for (int scc = 1; scc < statmentContextReferences; scc++)
754 mb.dup();
755 }
756
757 /**
758 Set the statement context to reflect we are running
759 System procedures, so that we can execute non-standard SQL.
760 */
761 if (isSystemCode) {
762 mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
763 "setSystemCode", "void", 0);
764 }
765
766 // for a function we need to fetch the current SQL control
767 // so that we can reset it once the function is complete.
768 //
769 if (isFunction)
770 {
771 functionEntrySQLAllowed = acb.newFieldDeclaration(Modifier.PRIVATE, "short");
772 mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
773 "getSQLAllowed", "short", 0);
774 mb.setField(functionEntrySQLAllowed);
775
776 }
777
778
779 // Set up the statement context to reflect the
780 // restricted SQL execution allowed by this routine.
781
782 mb.push(sqlAllowed);
783 mb.push(false);
784 mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
785 "setSQLAllowed", "void", 2);
786
787 }
788
789 // add in the ResultSet arrays.
790 if (routineInfo != null) {
791
792 int compiledResultSets = methodParameterTypes.length - methodParms.length;
793
794 if (compiledResultSets != 0) {
795
796 // Add a method that indicates the maxium number of dynamic result sets.
797 int maxDynamicResults = routineInfo.getMaxDynamicResultSets();
798 if (maxDynamicResults > 0) {
799 MethodBuilder gdr = acb.getClassBuilder().newMethodBuilder(Modifier.PUBLIC, "int", "getMaxDynamicResults");
800 gdr.push(maxDynamicResults);
801 gdr.methodReturn();
802 gdr.complete();
803 }
804
805 // add a method to return all the dynamic result sets (unordered)
806 MethodBuilder gdr = acb.getClassBuilder().newMethodBuilder(Modifier.PUBLIC, "java.sql.ResultSet[][]", "getDynamicResults");
807
808 MethodBuilder cons = acb.getConstructor();
809 // if (procDef.getParameterStyle() == RoutineAliasInfo.PS_JAVA)
810 {
811 // PARAMETER STYLE JAVA
812
813 LocalField procedureResultSetsHolder = acb.newFieldDeclaration(Modifier.PRIVATE, "java.sql.ResultSet[][]");
814
815 // getDynamicResults body
816 gdr.getField(procedureResultSetsHolder);
817
818 // create the holder of all the ResultSet arrays, new java.sql.ResultSet[][compiledResultSets]
819 cons.pushNewArray("java.sql.ResultSet[]", compiledResultSets);
820 cons.setField(procedureResultSetsHolder);
821
822
823 // arguments for the dynamic result sets
824 for (int i = 0; i < compiledResultSets; i++) {
825
826 mb.pushNewArray("java.sql.ResultSet", 1);
827 mb.dup();
828
829 mb.getField(procedureResultSetsHolder);
830 mb.swap();
831
832 mb.setArrayElement(i);
833 }
834 }
835
836 // complete the method that returns the ResultSet[][] to the
837 gdr.methodReturn();
838 gdr.complete();
839
840 nargs += compiledResultSets;
841 }
842
843 }
844
845 String javaReturnType = getJavaTypeName();
846
847 MethodBuilder mbnc = null;
848 MethodBuilder mbcm = mb;
849
850
851 // If any of the parameters are null then
852 // do not call the method, just return null.
853 if (returnsNullOnNullState != null)
854 {
855 mbnc = acb.newGeneratedFun(javaReturnType, Modifier.PRIVATE, methodParameterTypes);
856
857 // add the throws clause for the public static method we are going to call.
858 Class[] throwsSet = ((java.lang.reflect.Method) method).getExceptionTypes();
859 for (int te = 0; te < throwsSet.length; te++)
860 {
861 mbnc.addThrownException(throwsSet[te].getName());
862 }
863
864 mbnc.getField(returnsNullOnNullState);
865 mbnc.conditionalIf();
866
867 // set up for a null!!
868 // for objects is easy.
869 mbnc.pushNull(javaReturnType);
870
871 mbnc.startElseCode();
872
873 if (!actualMethodReturnType.equals(javaReturnType))
874 mbnc.pushNewStart(javaReturnType);
875
876 // fetch all the arguments
877 for (int pa = 0; pa < nargs; pa++)
878 {
879 mbnc.getParameter(pa);
880 }
881
882 mbcm = mbnc;
883 }
884
885 mbcm.callMethod(VMOpcode.INVOKESTATIC, method.getDeclaringClass().getName(), methodName,
886 actualMethodReturnType, nargs);
887
888
889 if (returnsNullOnNullState != null)
890 {
891 if (!actualMethodReturnType.equals(javaReturnType))
892 mbnc.pushNewComplete(1);
893
894 mbnc.completeConditional();
895
896 mbnc.methodReturn();
897 mbnc.complete();
898
899 // now call the wrapper method
900 mb.callMethod(VMOpcode.INVOKEVIRTUAL, acb.getClassBuilder().getFullName(), mbnc.getName(),
901 javaReturnType, nargs);
902 mbnc = null;
903 }
904
905
906 if (routineInfo != null) {
907
908 // reset the SQL allowed setting that we set upon
909 // entry to the method.
910 if (functionEntrySQLAllowed != null) {
911 acb.pushThisAsActivation(mb);
912 mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
913 "getLanguageConnectionContext", ClassName.LanguageConnectionContext, 0);
914 mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
915 "getStatementContext", "org.apache.derby.iapi.sql.conn.StatementContext", 0);
916 mb.getField(functionEntrySQLAllowed);
917 mb.push(true); // override as we are ending the control set by this function all.
918 mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
919 "setSQLAllowed", "void", 2);
920
921 }
922
923 if (outParamArrays != null) {
924
925 MethodBuilder constructor = acb.getConstructor();
926
927 // constructor - setting up correct paramter type info
928 acb.pushThisAsActivation(constructor);
929 constructor.callMethod(VMOpcode.INVOKEINTERFACE, null,
930 "getParameterValueSet", ClassName.ParameterValueSet, 0);
931
932 // execute - passing out parameters back.
933 acb.pushThisAsActivation(mb);
934 mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
935 "getParameterValueSet", ClassName.ParameterValueSet, 0);
936
937 int[] parameterModes = routineInfo.getParameterModes();
938 for (int i = 0; i < outParamArrays.length; i++) {
939
940 int parameterMode = parameterModes[i];
941 if (parameterMode != JDBC30Translation.PARAMETER_MODE_IN) {
942
943 // must be a parameter if it is INOUT or OUT.
944 ValueNode sqlParamNode = ((SQLToJavaValueNode) methodParms[i]).getSQLValueNode();
945
946
947 int applicationParameterNumber = applicationParameterNumbers[i];
948
949 // Set the correct parameter nodes in the ParameterValueSet at constructor time.
950 constructor.dup();
951 constructor.push(applicationParameterNumber);
952 constructor.push(parameterMode);
953 constructor.callMethod(VMOpcode.INVOKEINTERFACE, null,
954 "setParameterMode", "void", 2);
955
956 // Pass the value of the outparameters back to the calling code
957 LocalField lf = outParamArrays[i];
958
959 mb.dup();
960 mb.push(applicationParameterNumber);
961 mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
962 "getParameter", ClassName.DataValueDescriptor, 1);
963
964 // see if we need to set the desired length/scale/precision of the type
965 DataTypeDescriptor paramdtd = sqlParamNode.getTypeServices();
966
967 boolean isNumericType = paramdtd.getTypeId().isNumericTypeId();
968
969 // is the underlying type for the OUT/INOUT parameter primitive.
970 boolean isPrimitive = ((java.lang.reflect.Method) method).getParameterTypes()[i].getComponentType().isPrimitive();
971
972 if (isNumericType) {
973 // need to up-cast as the setValue(Number) method only exists on NumberDataValue
974
975 if (!isPrimitive)
976 mb.cast(ClassName.NumberDataValue);
977 }
978 else if (paramdtd.getTypeId().isBooleanTypeId())
979 {
980 // need to cast as the setValue(Boolean) method only exists on BooleanDataValue
981 if (!isPrimitive)
982 mb.cast(ClassName.BooleanDataValue);
983 }
984
985 if (paramdtd.getTypeId().variableLength()) {
986 // need another DVD reference for the set width below.
987 mb.dup();
988 }
989
990
991 mb.getField(lf); // pvs, dvd, array
992 mb.getArrayElement(0); // pvs, dvd, value
993
994 // The value needs to be set thorugh the setValue(Number) method.
995 if (isNumericType && !isPrimitive)
996 {
997 mb.upCast("java.lang.Number");
998 }
999
1000 mb.callMethod(VMOpcode.INVOKEINTERFACE, null, "setValue", "void", 1);
1001
1002 if (paramdtd.getTypeId().variableLength()) {
1003 mb.push(isNumericType ? paramdtd.getPrecision() : paramdtd.getMaximumWidth());
1004 mb.push(paramdtd.getScale());
1005 mb.push(isNumericType);
1006 mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.VariableSizeDataValue, "setWidth", ClassName.DataValueDescriptor, 3);
1007 mb.endStatement();
1008 }
1009 }
1010 }
1011 constructor.endStatement();
1012 mb.endStatement();
1013 }
1014
1015 }
1016 }
1017}