001: /* Copyright (c) 1995-2000, The Hypersonic SQL Group.
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the Hypersonic SQL Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: *
030: * This software consists of voluntary contributions made by many individuals
031: * on behalf of the Hypersonic SQL Group.
032: *
033: *
034: * For work added by the HSQL Development Group:
035: *
036: * Copyright (c) 2001-2005, The HSQL Development Group
037: * All rights reserved.
038: *
039: * Redistribution and use in source and binary forms, with or without
040: * modification, are permitted provided that the following conditions are met:
041: *
042: * Redistributions of source code must retain the above copyright notice, this
043: * list of conditions and the following disclaimer.
044: *
045: * Redistributions in binary form must reproduce the above copyright notice,
046: * this list of conditions and the following disclaimer in the documentation
047: * and/or other materials provided with the distribution.
048: *
049: * Neither the name of the HSQL Development Group nor the names of its
050: * contributors may be used to endorse or promote products derived from this
051: * software without specific prior written permission.
052: *
053: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
054: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
055: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
056: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
057: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
058: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
059: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
060: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
061: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
062: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
063: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
064: */
065:
066: package org.hsqldb;
067:
068: import java.lang.reflect.InvocationTargetException;
069: import java.lang.reflect.Method;
070: import java.lang.reflect.Modifier;
071: import java.sql.Connection;
072:
073: import org.hsqldb.lib.HashMap;
074: import org.hsqldb.lib.HsqlArrayList;
075: import org.hsqldb.lib.StringConverter;
076: import org.hsqldb.types.Binary;
077: import org.hsqldb.types.JavaObject;
078:
079: // fredt@users 20020912 - patch 1.7.1 - shortcut treatment of identity() call
080: // fredt@users 20020912 - patch 1.7.1 - cache java.lang.reflect.Method objects
081: // fredt@users 20021013 - patch 1.7.1 - ignore non-static methods
082: // boucherb@users 20030201 - patch 1.7.2 - direct calls for org.hsqldb.Library
083: // fredt@users 20030621 - patch 1.7.2 - shortcut treatment of session calls
084: // boucherb@users 200404xx - doc 1.7.2 - updates toward 1.7.2 final
085:
086: /**
087: * Provides services to evaluate SQL function and stored procedure calls,
088: * by invoking Java methods.
089: *
090: * Extended in successive versions of HSQLDB.
091: *
092: * @author Thomas Mueller (Hypersonic SQL Group)
093: * @version 1.8.0
094: * @since Hypersonic SQL
095: */
096: class Function {
097:
098: private String sFunction;
099: private Method mMethod;
100: private String returnClassName;
101: private Class[] aArgClasses;
102: private int iReturnType;
103: private int iArgCount;
104: private int iSqlArgCount;
105: private int iSqlArgStart;
106: private int[] iArgType;
107: private boolean[] bArgNullable;
108: Expression[] eArg;
109: private boolean bConnection;
110: private static HashMap methodCache = new HashMap();
111: private int fID;
112: String name; // name used to call function
113: boolean isSimple; //CURRENT_TIME, NOW etc.
114: boolean hasAggregate;
115:
116: /**
117: * Constructs a new Function object with the given function call name
118: * and using the specified Session context. <p>
119: *
120: * The call name is the fully qualified name of a static Java method, in
121: * the form "package.class.method." This implies that Java
122: * methods with the same fully qualified name but different signatures
123: * cannot be used properly as HSQLDB SQL functions or stored procedures.
124: * For instance, it is impossible to call both System.getProperty(String)
125: * and System.getProperty(String,String) under this arrangement, because
126: * the HSQLDB Function object is unable to differentiate between the two;
127: * it simply chooses the first method matching the FQN in the array of
128: * methods obtained from calling getMethods() on an instance of the
129: * Class indicated in the FQN, hiding all other methods with the same
130: * FQN. <p>
131: *
132: * The function FQN must match at least one static Java method FQN in the
133: * specified class or construction cannot procede and an HsqlException is
134: * thrown. <p>
135: *
136: * The isSimple parameter is true when certain SQL standard functions
137: * that are used without brackets are invokded.
138: *
139: * @param name this Function object's call name
140: * @param fqn the fully qualified name of a Java method
141: * @param isSimple if true, used to evalate CURRENT_TIME, NOW etc.
142: * evaluate
143: * @throws HsqlException if the specified function FQN corresponds to no
144: * Java method
145: */
146: Function(String name, String fqn, boolean isSimple)
147: throws HsqlException {
148:
149: this .name = name;
150: this .isSimple = isSimple;
151:
152: // cSession = session;
153: sFunction = fqn;
154: fID = Library.functionID(fqn);
155:
156: int i = fqn.lastIndexOf('.');
157:
158: Trace.check(i != -1, Trace.UNEXPECTED_TOKEN, fqn);
159:
160: String classname = fqn.substring(0, i);
161:
162: mMethod = (Method) methodCache.get(fqn);
163:
164: if (mMethod == null) {
165: String methodname = fqn.substring(i + 1);
166: Class classinstance = null;
167:
168: try {
169: classinstance = Class.forName(classname);
170: } catch (Exception e) {
171: throw Trace.error(Trace.FUNCTION_NOT_FOUND,
172: Trace.Message_Pair,
173: new Object[] { classname, e });
174: }
175:
176: // public only, but includes those inherited from
177: // superclasses and superinterfaces. List is unordered.
178: Method[] methods = classinstance.getMethods();
179:
180: for (i = 0; i < methods.length; i++) {
181: Method m = methods[i];
182:
183: if (m.getName().equals(methodname)
184: && Modifier.isStatic(m.getModifiers())) {
185: mMethod = m;
186:
187: break;
188: }
189: }
190:
191: Trace.check(mMethod != null, Trace.UNKNOWN_FUNCTION,
192: methodname);
193: methodCache.put(fqn, mMethod);
194: }
195:
196: Class returnClass = mMethod.getReturnType();
197:
198: if (returnClass.equals(org.hsqldb.Result.class)) {
199:
200: // For now, we can write stored procedures whose
201: // descriptor explicitly specifies the above return type.
202: // Later, this will be modified or replaced to provide proper
203: // support for jdbcCallableStatement OUT mode return parameter,
204: // multiple results (Result.MULTI etc.)
205: iReturnType = Types.OTHER;
206: } else {
207:
208: // Now we support the following construction-time return type
209: // Classes, as specified by the method descriptor:
210: //
211: // 1.) any primitive or primitive wrapper type, except Byte(.TYPE),
212: // Short(.TYPE) and Float(.TYPE) (TBD; narrow if no truncation)
213: //
214: // 2.) any primitive array type
215: //
216: // 3.) any non-primitive array whose base component implements
217: // java.io.Serializable
218: //
219: // 4.) any class implementing java.io.Serializable, except those
220: // described in 1.) as currently unsupported
221: //
222: // 5.) java.lang.Object
223: //
224: // For java.lang.Object, checking is deferred from the construction
225: // stage to the evaluation stage. In general, for the evaluation
226: // to succeed, the runtime class of the retrieved Object must be
227: //
228: // 1.) any primitive or primitive wrapper type, except Byte(.TYPE),
229: // Short(.TYPE) and Float(.TYPE) (TBD; narrow if no trunction)
230: //
231: // 2.) any primitive array type
232: // 3.) any non-primitive array whose base component implements
233: // java.io.Serializable
234: //
235: // 4.) any class implementing java.io.Serializable, except those
236: // described in 1.) as currently unsupported
237: //
238: // Additionally, it is possible for the evaluation to succeed under
239: // an SQL CALL if the runtime Class of the returned Object is not
240: // from the list above but is from the list below:
241: //
242: // 1.) is org.hsqldb.Result
243: // 2.) is org.hsqldb.jdbc.jdbcResultSet
244: //
245: // In these special cases, the statement executor notices the
246: // types and presents the client with a view the underlying result
247: // rather than with a view of the object as an opaque scalar value
248: //
249: iReturnType = Types.getParameterTypeNr(returnClass);
250: }
251:
252: returnClassName = Types.getFunctionReturnClassName(returnClass
253: .getName());
254: aArgClasses = mMethod.getParameterTypes();
255: iArgCount = aArgClasses.length;
256: iArgType = new int[iArgCount];
257: bArgNullable = new boolean[iArgCount];
258:
259: for (i = 0; i < aArgClasses.length; i++) {
260: Class a = aArgClasses[i];
261: String type = a.getName();
262:
263: if ((i == 0) && a.equals(Connection.class)) {
264:
265: // TODO: provide jdbc:default:connection url functionality
266: //
267: // only the first parameter can be a Connection
268: bConnection = true;
269: } else {
270:
271: // see discussion above for iReturnType
272: iArgType[i] = Types.getParameterTypeNr(a);
273: bArgNullable[i] = !a.isPrimitive();
274: }
275: }
276:
277: iSqlArgCount = iArgCount;
278:
279: if (bConnection) {
280: iSqlArgCount--;
281:
282: iSqlArgStart = 1;
283: } else {
284: iSqlArgStart = 0;
285: }
286:
287: eArg = new Expression[iArgCount];
288: }
289:
290: /**
291: * Evaluates and returns this Function in the context of the session.<p>
292: */
293: Object getValue(Session session) throws HsqlException {
294:
295: switch (fID) {
296:
297: case Library.curtime:
298: return session.getCurrentTime();
299:
300: case Library.curdate:
301: return session.getCurrentDate();
302:
303: case Library.database:
304: return session.getDatabase().getPath();
305:
306: case Library.getAutoCommit:
307: return session.isAutoCommit() ? Boolean.TRUE
308: : Boolean.FALSE;
309:
310: case Library.identity:
311: return session.getLastIdentity();
312:
313: case Library.isReadOnlyDatabase:
314: return session.getDatabase().databaseReadOnly ? Boolean.TRUE
315: : Boolean.FALSE;
316:
317: case Library.isReadOnlyConnection:
318: return session.isReadOnly() ? Boolean.TRUE : Boolean.FALSE;
319:
320: case Library.isReadOnlyDatabaseFiles:
321: return session.getDatabase().isFilesReadOnly() ? Boolean.TRUE
322: : Boolean.FALSE;
323:
324: case Library.now:
325: return session.getCurrentTimestamp();
326:
327: case Library.user:
328: return session.getUsername();
329: }
330:
331: Object[] oArg = getArguments(session);
332:
333: if (oArg == null) {
334: return null;
335: }
336:
337: return getValue(session, oArg);
338: }
339:
340: /**
341: * Evaluates the Function with the given arguments in the session context.
342: */
343: Object getValue(Session session, Object[] arguments)
344: throws HsqlException {
345:
346: if (bConnection) {
347: arguments[0] = session.getInternalConnection();
348: }
349:
350: try {
351: Object ret = (fID >= 0) ? Library.invoke(fID, arguments)
352: : mMethod.invoke(null, arguments);
353:
354: return Column.convertObject(ret, iReturnType);
355: } catch (InvocationTargetException e) {
356:
357: // thrown by user functions
358: Throwable t = e.getTargetException();
359: String s = sFunction + " : " + t.toString();
360:
361: throw Trace.error(Trace.FUNCTION_CALL_ERROR, s);
362: } catch (IllegalAccessException e) {
363:
364: // never thrown in this method
365: throw Trace.error(Trace.FUNCTION_CALL_ERROR);
366: }
367:
368: // Library function throw HsqlException
369: }
370:
371: private Object[] getArguments(Session session) throws HsqlException {
372:
373: int i = bConnection ? 1 : 0;
374: Object[] oArg = new Object[iArgCount];
375:
376: for (; i < iArgCount; i++) {
377: Expression e = eArg[i];
378: Object o = null;
379:
380: if (e != null) {
381:
382: // no argument: null
383: o = e.getValue(session, iArgType[i]);
384: }
385:
386: if ((o == null) && !bArgNullable[i]) {
387:
388: // null argument for primitive datatype: don't call
389: return null;
390: }
391:
392: if (o instanceof JavaObject) {
393: o = ((JavaObject) o).getObject();
394: } else if (o instanceof Binary) {
395: o = ((Binary) o).getBytes();
396: }
397:
398: oArg[i] = o;
399: }
400:
401: return oArg;
402: }
403:
404: /**
405: * returns null if any non-nullable element of values is null
406: */
407: private Object[] getNotNull(Object[] values) throws HsqlException {
408:
409: int i = bConnection ? 1 : 0;
410:
411: for (; i < iArgCount; i++) {
412: Object o = values[i];
413:
414: if (o == null && !bArgNullable[i]) {
415:
416: // null argument for primitive datatype: don't call
417: return null;
418: }
419: }
420:
421: return values;
422: }
423:
424: void collectInGroupByExpressions(HsqlArrayList colExps) {
425:
426: for (int i = 0; i < iArgCount; i++) {
427: Expression e = eArg[i];
428:
429: if (e != null) {
430: e.collectInGroupByExpressions(colExps);
431: }
432: }
433: }
434:
435: Object getAggregatedValue(Session session, Object currValue)
436: throws HsqlException {
437:
438: Object[] valueArray = (Object[]) currValue;
439:
440: if (valueArray == null) {
441: valueArray = new Object[iArgCount];
442: }
443:
444: for (int i = 0; i < iArgCount; i++) {
445: Expression e = eArg[i];
446:
447: if (eArg[i] != null) {
448: if (eArg[i].isAggregate()) {
449: valueArray[i] = Column
450: .convertObject(e.getAggregatedValue(
451: session, valueArray[i]),
452: iArgType[i]);
453: } else {
454: valueArray[i] = e.getValue(session, iArgType[i]);
455: }
456: }
457: }
458:
459: valueArray = getNotNull(valueArray);
460:
461: if (valueArray == null) {
462: return null;
463: }
464:
465: return getValue(session, valueArray);
466: }
467:
468: Object updateAggregatingValue(Session session, Object currValue)
469: throws HsqlException {
470:
471: Object[] valueArray = (Object[]) currValue;
472:
473: if (valueArray == null) {
474: valueArray = new Object[iArgCount];
475: }
476:
477: for (int i = 0; i < iArgCount; i++) {
478: Expression e = eArg[i];
479:
480: if (eArg[i] != null) {
481: valueArray[i] = e.updateAggregatingValue(session,
482: valueArray[i]);
483: }
484: }
485:
486: return valueArray;
487: }
488:
489: /**
490: * Returns the number of parameters that must be supplied to evaluate
491: * this Function object from SQL. <p>
492: *
493: * This value may be different than the number of parameters of the
494: * underlying Java method. This is because HSQLDB automatically detects
495: * if the first parameter is of type java.sql.Connection, and supplies a
496: * live Connection object constructed from the evaluating session context
497: * if so.
498: */
499: int getArgCount() {
500: return iSqlArgCount;
501: }
502:
503: /**
504: * Remnoves the Table filters from Expression parameters to this Function.
505: *
506: * @throws HsqlException if there is a problem resolving a parameter
507: * against the specified TableFilter
508: */
509: /*
510: void removeFilters() throws HsqlException {
511:
512: Expression e;
513:
514: for (int i = iSqlArgStart; i < iArgCount; i++) {
515: e = eArg[i];
516:
517: if (e != null) {
518: e.removeFilters();
519: }
520: }
521: }
522: */
523: void replaceAliases(Expression[] columns, int length)
524: throws HsqlException {
525:
526: Expression e;
527:
528: for (int i = iSqlArgStart; i < iArgCount; i++) {
529: e = eArg[i];
530:
531: if (e != null) {
532: if (e.exprType == Expression.COLUMN) {
533: eArg[i] = e.getExpressionForAlias(columns, length);
534: } else {
535: e.replaceAliases(columns, length);
536: }
537: }
538: }
539: }
540:
541: /**
542: * Checks the Expresion parameters to this Function object against the
543: * set of TableFilter.
544: */
545: void checkTables(HsqlArrayList fa) throws HsqlException {
546:
547: Expression e;
548:
549: for (int i = iSqlArgStart; i < iArgCount; i++) {
550: e = eArg[i];
551:
552: if (e != null) {
553: e.checkTables(fa);
554: }
555: }
556: }
557:
558: /**
559: * Resolves the Expression parameters to this Function object against the
560: * specified TableFilter.
561: */
562: void resolveTables(TableFilter f) throws HsqlException {
563:
564: Expression e;
565:
566: for (int i = iSqlArgStart; i < iArgCount; i++) {
567: e = eArg[i];
568:
569: if (e != null) {
570: e.resolveTables(f);
571: }
572: }
573: }
574:
575: /**
576: * Resolves the type of this expression and performs certain
577: * transformations and optimisations of the expression tree.
578: */
579: void resolveType(Session session) throws HsqlException {
580:
581: Expression e;
582:
583: for (int i = iSqlArgStart; i < iArgCount; i++) {
584: e = eArg[i];
585:
586: if (e != null) {
587: if (e.isParam()) {
588: e.setDataType(iArgType[i]);
589:
590: e.nullability = getArgNullability(i);
591: e.valueClassName = getArgClass(i).getName();
592: } else {
593: e.resolveTypes(session);
594: }
595: }
596: }
597: }
598:
599: /**
600: * Checks each of this object's arguments for resolution, throwing an
601: * HsqlException if any arguments have not yet been resolved. <p>
602: * The check boolean argument is passed on to further check calls.<p>
603: */
604: boolean checkResolved(boolean check) throws HsqlException {
605:
606: boolean result = true;
607:
608: for (int i = iSqlArgStart; i < iArgCount; i++) {
609: if (eArg[i] != null) {
610: result = result && eArg[i].checkResolved(check);
611: }
612: }
613:
614: return result;
615: }
616:
617: /**
618: * Returns the type of the argument at the specified
619: * offset in this Function object's paramter list. <p>
620: */
621: int getArgType(int i) {
622: return iArgType[i];
623: }
624:
625: /**
626: * Returns the type of this Function
627: * object's return type. <p>
628: */
629: int getReturnType() {
630: return iReturnType;
631: }
632:
633: /**
634: * Binds the specified expression to the specified position in this
635: * Function object's parameter list. <p>
636: */
637: void setArgument(int i, Expression e) {
638:
639: if (bConnection) {
640: i++;
641: }
642:
643: eArg[i] = e;
644: hasAggregate = hasAggregate || (e != null && e.isAggregate());
645: }
646:
647: /**
648: * Returns a DDL representation of this object. <p>
649: */
650: String getDLL() throws HsqlException {
651:
652: StringBuffer sb = new StringBuffer();
653:
654: // get the name as used by the CHECK statement
655: String ddlName = name;
656:
657: if (isSimple) {
658: return name;
659: } else if (Token.T_TRIM.equals(name)) {
660:
661: // special case for TRIM
662: sb.append(name).append('(');
663:
664: boolean leading = eArg[2].testCondition(null);
665: boolean trailing = eArg[3].testCondition(null);
666:
667: if (leading && trailing) {
668: sb.append(Token.T_BOTH);
669: } else {
670: sb.append(leading ? Token.T_LEADING : Token.T_TRAILING);
671: }
672:
673: // to do change to string
674: sb.append(' ');
675:
676: String charval = (String) eArg[1].getValue(null);
677:
678: sb.append(Column.createSQLString(charval)).append(' ');
679: sb.append(Token.T_FROM).append(' ');
680: sb.append(eArg[0].getDDL()).append(')');
681:
682: return sb.toString();
683: }
684:
685: if (sFunction.equals(name)) {
686: ddlName = StringConverter.toQuotedString(name, '"', true);
687: }
688:
689: sb.append(ddlName).append('(');
690:
691: for (int i = iSqlArgStart; i < eArg.length; i++) {
692: sb.append(eArg[i].getDDL());
693:
694: if (i < eArg.length - 1) {
695: sb.append(',');
696: }
697: }
698:
699: sb.append(')');
700:
701: return sb.toString();
702: }
703:
704: /**
705: * Returns a String representation of this object. <p>
706: */
707: public String describe(Session session) {
708:
709: StringBuffer sb = new StringBuffer();
710:
711: sb.append(super .toString()).append("=[\n");
712: sb.append(sFunction).append("(");
713:
714: for (int i = iSqlArgStart; i < eArg.length; i++) {
715: sb.append("[").append(eArg[i].describe(session))
716: .append("]");
717: }
718:
719: sb.append(") returns ").append(
720: Types.getTypeString(getReturnType()));
721: sb.append("]\n");
722:
723: return sb.toString();
724: }
725:
726: /**
727: * Returns the Java Class of the object returned by getValue(). <p>
728: */
729: String getReturnClassName() {
730: return returnClassName;
731: }
732:
733: /**
734: * Returns the Java Class of the i'th argument. <p>
735: */
736: Class getArgClass(int i) {
737: return aArgClasses[i];
738: }
739:
740: /**
741: * Returns the SQL nullability code of the i'th argument. <p>
742: */
743: int getArgNullability(int i) {
744: return bArgNullable[i] ? Expression.NULLABLE
745: : Expression.NO_NULLS;
746: }
747:
748: Method getMethod() {
749: return mMethod;
750: }
751: }
|