/* Copyright (c) 1995-2000, The Hypersonic SQL Group. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the Hypersonic SQL Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * on behalf of the Hypersonic SQL Group. * * * For work added by the HSQL Development Group: * * Copyright (c) 2001-2008, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.sql.Connection; import org.hsqldb.lib.HashMap; import org.hsqldb.lib.HsqlArrayList; import org.hsqldb.lib.StringConverter; import org.hsqldb.types.Binary; import org.hsqldb.types.JavaObject; // fredt@users 20020912 - patch 1.7.1 - shortcut treatment of identity() call // fredt@users 20020912 - patch 1.7.1 - cache java.lang.reflect.Method objects // fredt@users 20021013 - patch 1.7.1 - ignore non-static methods // boucherb@users 20030201 - patch 1.7.2 - direct calls for org.hsqldb.Library // fredt@users 20030621 - patch 1.7.2 - shortcut treatment of session calls // boucherb@users 200404xx - doc 1.7.2 - updates toward 1.7.2 final /** * Provides services to evaluate SQL function and stored procedure calls, * by invoking Java methods. * * Extended in successive versions of HSQLDB. * * @author Thomas Mueller (Hypersonic SQL Group) * @version 1.8.0 * @since Hypersonic SQL */ class Function { private String sFunction; private Method mMethod; private String returnClassName; private Class[] aArgClasses; private int iReturnType; private int iArgCount; private int iSqlArgCount; private int iSqlArgStart; private int[] iArgType; private boolean[] bArgNullable; Expression[] eArg; private boolean bConnection; private static HashMap methodCache = new HashMap(); private int fID; String name; // name used to call function boolean isSimple; //CURRENT_TIME, NOW etc. boolean hasAggregate; /** * Constructs a new Function object with the given function call name * and using the specified Session context. <p> * * The call name is the fully qualified name of a static Java method, in * the form "package.class.method." This implies that Java * methods with the same fully qualified name but different signatures * cannot be used properly as HSQLDB SQL functions or stored procedures. * For instance, it is impossible to call both System.getProperty(String) * and System.getProperty(String,String) under this arrangement, because * the HSQLDB Function object is unable to differentiate between the two; * it simply chooses the first method matching the FQN in the array of * methods obtained from calling getMethods() on an instance of the * Class indicated in the FQN, hiding all other methods with the same * FQN. <p> * * The function FQN must match at least one static Java method FQN in the * specified class or construction cannot procede and an HsqlException is * thrown. <p> * * The isSimple parameter is true when certain SQL standard functions * that are used without brackets are invokded. * * @param name this Function object's call name * @param fqn the fully qualified name of a Java method * @param isSimple if true, used to evalate CURRENT_TIME, NOW etc. * evaluate * @throws HsqlException if the specified function FQN corresponds to no * Java method */ Function(String name, String fqn, boolean isSimple) throws HsqlException { this.name = name; this.isSimple = isSimple; // cSession = session; sFunction = fqn; fID = Library.functionID(fqn); int i = fqn.lastIndexOf('.'); Trace.check(i != -1, Trace.UNEXPECTED_TOKEN, fqn); String classname = fqn.substring(0, i); mMethod = (Method) methodCache.get(fqn); if (mMethod == null) { String methodname = fqn.substring(i + 1); Class classinstance = null; try { classinstance = Class.forName(classname); } catch (Exception e) { throw Trace.error(Trace.FUNCTION_NOT_FOUND, Trace.Message_Pair, new Object[] { classname, e }); } // public only, but includes those inherited from // superclasses and superinterfaces. List is unordered. Method[] methods = classinstance.getMethods(); for (i = 0; i < methods.length; i++) { Method m = methods[i]; if (m.getName().equals(methodname) && Modifier.isStatic(m.getModifiers())) { mMethod = m; break; } } Trace.check(mMethod != null, Trace.UNKNOWN_FUNCTION, methodname); methodCache.put(fqn, mMethod); } Class returnClass = mMethod.getReturnType(); if (returnClass.equals(org.hsqldb.Result.class)) { // For now, we can write stored procedures whose // descriptor explicitly specifies the above return type. // Later, this will be modified or replaced to provide proper // support for jdbcCallableStatement OUT mode return parameter, // multiple results (Result.MULTI etc.) iReturnType = Types.OTHER; } else { // Now we support the following construction-time return type // Classes, as specified by the method descriptor: // // 1.) any primitive or primitive wrapper type, except Byte(.TYPE), // Short(.TYPE) and Float(.TYPE) (TBD; narrow if no truncation) // // 2.) any primitive array type // // 3.) any non-primitive array whose base component implements // java.io.Serializable // // 4.) any class implementing java.io.Serializable, except those // described in 1.) as currently unsupported // // 5.) java.lang.Object // // For java.lang.Object, checking is deferred from the construction // stage to the evaluation stage. In general, for the evaluation // to succeed, the runtime class of the retrieved Object must be // // 1.) any primitive or primitive wrapper type, except Byte(.TYPE), // Short(.TYPE) and Float(.TYPE) (TBD; narrow if no trunction) // // 2.) any primitive array type // 3.) any non-primitive array whose base component implements // java.io.Serializable // // 4.) any class implementing java.io.Serializable, except those // described in 1.) as currently unsupported // // Additionally, it is possible for the evaluation to succeed under // an SQL CALL if the runtime Class of the returned Object is not // from the list above but is from the list below: // // 1.) is org.hsqldb.Result // 2.) is org.hsqldb.jdbc.jdbcResultSet // // In these special cases, the statement executor notices the // types and presents the client with a view the underlying result // rather than with a view of the object as an opaque scalar value // iReturnType = Types.getParameterTypeNr(returnClass); } returnClassName = Types.getFunctionReturnClassName(returnClass.getName()); aArgClasses = mMethod.getParameterTypes(); iArgCount = aArgClasses.length; iArgType = new int[iArgCount]; bArgNullable = new boolean[iArgCount]; for (i = 0; i < aArgClasses.length; i++) { Class a = aArgClasses[i]; String type = a.getName(); if ((i == 0) && a.equals(Connection.class)) { // TODO: provide jdbc:default:connection url functionality // // only the first parameter can be a Connection bConnection = true; } else { // see discussion above for iReturnType iArgType[i] = Types.getParameterTypeNr(a); bArgNullable[i] = !a.isPrimitive(); } } iSqlArgCount = iArgCount; if (bConnection) { iSqlArgCount--; iSqlArgStart = 1; } else { iSqlArgStart = 0; } eArg = new Expression[iArgCount]; } /** * Evaluates and returns this Function in the context of the session.<p> */ Object getValue(Session session) throws HsqlException { switch (fID) { case Library.curtime : return session.getCurrentTime(); case Library.curdate : return session.getCurrentDate(); case Library.database : return session.getDatabase().getPath(); case Library.getAutoCommit : return session.isAutoCommit() ? Boolean.TRUE : Boolean.FALSE; case Library.identity : return session.getLastIdentity(); case Library.isReadOnlyDatabase : return session.getDatabase().databaseReadOnly ? Boolean.TRUE : Boolean.FALSE; case Library.isReadOnlyConnection : return session.isReadOnly() ? Boolean.TRUE : Boolean.FALSE; case Library.isReadOnlyDatabaseFiles : return session.getDatabase().isFilesReadOnly() ? Boolean.TRUE : Boolean .FALSE; case Library.now : return session.getCurrentTimestamp(); case Library.user : return session.getUsername(); } Object[] oArg = getArguments(session); if (oArg == null) { return null; } return getValue(session, oArg); } /** * Evaluates the Function with the given arguments in the session context. */ Object getValue(Session session, Object[] arguments) throws HsqlException { if (bConnection) { arguments[0] = session.getInternalConnection(); } try { Object ret = (fID >= 0) ? Library.invoke(fID, arguments) : mMethod.invoke(null, arguments); return Column.convertObject(ret, iReturnType); } catch (InvocationTargetException e) { // thrown by user functions Throwable t = e.getTargetException(); String s = sFunction + " : " + t.toString(); throw Trace.error(Trace.FUNCTION_CALL_ERROR, s); } catch (IllegalAccessException e) { // never thrown in this method throw Trace.error(Trace.FUNCTION_CALL_ERROR); } // Library function throw HsqlException } private Object[] getArguments(Session session) throws HsqlException { int i = bConnection ? 1 : 0; Object[] oArg = new Object[iArgCount]; for (; i < iArgCount; i++) { Expression e = eArg[i]; Object o = null; if (e != null) { // no argument: null o = e.getValue(session, iArgType[i]); } if ((o == null) &&!bArgNullable[i]) { // null argument for primitive datatype: don't call return null; } if (o instanceof JavaObject) { o = ((JavaObject) o).getObject(); } else if (o instanceof Binary) { o = ((Binary) o).getBytes(); } oArg[i] = o; } return oArg; } /** * returns null if any non-nullable element of values is null */ private Object[] getNotNull(Object[] values) throws HsqlException { int i = bConnection ? 1 : 0; for (; i < iArgCount; i++) { Object o = values[i]; if (o == null &&!bArgNullable[i]) { // null argument for primitive datatype: don't call return null; } } return values; } void collectInGroupByExpressions(HsqlArrayList colExps) { for (int i = 0; i < iArgCount; i++) { Expression e = eArg[i]; if (e != null) { e.collectInGroupByExpressions(colExps); } } } Object getAggregatedValue(Session session, Object currValue) throws HsqlException { Object[] valueArray = (Object[]) currValue; if (valueArray == null) { valueArray = new Object[iArgCount]; } for (int i = 0; i < iArgCount; i++) { Expression e = eArg[i]; if (eArg[i] != null) { if (eArg[i].isAggregate()) { valueArray[i] = Column.convertObject( e.getAggregatedValue(session, valueArray[i]), iArgType[i]); } else { valueArray[i] = e.getValue(session, iArgType[i]); } } } valueArray = getNotNull(valueArray); if (valueArray == null) { return null; } return getValue(session, valueArray); } Object updateAggregatingValue(Session session, Object currValue) throws HsqlException { Object[] valueArray = (Object[]) currValue; if (valueArray == null) { valueArray = new Object[iArgCount]; } for (int i = 0; i < iArgCount; i++) { Expression e = eArg[i]; if (eArg[i] != null) { valueArray[i] = e.updateAggregatingValue(session, valueArray[i]); } } return valueArray; } /** * Returns the number of parameters that must be supplied to evaluate * this Function object from SQL. <p> * * This value may be different than the number of parameters of the * underlying Java method. This is because HSQLDB automatically detects * if the first parameter is of type java.sql.Connection, and supplies a * live Connection object constructed from the evaluating session context * if so. */ int getArgCount() { return iSqlArgCount; } /** * Remnoves the Table filters from Expression parameters to this Function. * * @throws HsqlException if there is a problem resolving a parameter * against the specified TableFilter */ /* void removeFilters() throws HsqlException { Expression e; for (int i = iSqlArgStart; i < iArgCount; i++) { e = eArg[i]; if (e != null) { e.removeFilters(); } } } */ void replaceAliases(Expression[] columns, int length) throws HsqlException { Expression e; for (int i = iSqlArgStart; i < iArgCount; i++) { e = eArg[i]; if (e != null) { if (e.exprType == Expression.COLUMN) { eArg[i] = e.getExpressionForAlias(columns, length); } else { e.replaceAliases(columns, length); } } } } /** * Checks the Expresion parameters to this Function object against the * set of TableFilter. */ void checkTables(HsqlArrayList fa) throws HsqlException { Expression e; for (int i = iSqlArgStart; i < iArgCount; i++) { e = eArg[i]; if (e != null) { e.checkTables(fa); } } } /** * Resolves the Expression parameters to this Function object against the * specified TableFilter. */ void resolveTables(TableFilter f) throws HsqlException { Expression e; for (int i = iSqlArgStart; i < iArgCount; i++) { e = eArg[i]; if (e != null) { e.resolveTables(f); } } } /** * Resolves the type of this expression and performs certain * transformations and optimisations of the expression tree. */ void resolveType(Session session) throws HsqlException { Expression e; for (int i = iSqlArgStart; i < iArgCount; i++) { e = eArg[i]; if (e != null) { if (e.isParam()) { e.setDataType(iArgType[i]); e.nullability = getArgNullability(i); e.valueClassName = getArgClass(i).getName(); } else { e.resolveTypes(session); } } } } /** * Checks each of this object's arguments for resolution, throwing an * HsqlException if any arguments have not yet been resolved. <p> * The check boolean argument is passed on to further check calls.<p> */ boolean checkResolved(boolean check) throws HsqlException { boolean result = true; for (int i = iSqlArgStart; i < iArgCount; i++) { if (eArg[i] != null) { result = result && eArg[i].checkResolved(check); } } return result; } /** * Returns the type of the argument at the specified * offset in this Function object's paramter list. <p> */ int getArgType(int i) { return iArgType[i]; } /** * Returns the type of this Function * object's return type. <p> */ int getReturnType() { return iReturnType; } /** * Binds the specified expression to the specified position in this * Function object's parameter list. <p> */ void setArgument(int i, Expression e) { if (bConnection) { i++; } eArg[i] = e; hasAggregate = hasAggregate || (e != null && e.isAggregate()); } /** * Returns a DDL representation of this object. <p> */ String getDLL() throws HsqlException { StringBuffer sb = new StringBuffer(); // get the name as used by the CHECK statement String ddlName = name; if (isSimple) { return name; } else if (Token.T_TRIM.equals(name)) { // special case for TRIM sb.append(name).append('('); boolean leading = eArg[2].testCondition(null); boolean trailing = eArg[3].testCondition(null); if (leading && trailing) { sb.append(Token.T_BOTH); } else { sb.append(leading ? Token.T_LEADING : Token.T_TRAILING); } // to do change to string sb.append(' '); String charval = (String) eArg[1].getValue(null); sb.append(Column.createSQLString(charval)).append(' '); sb.append(Token.T_FROM).append(' '); sb.append(eArg[0].getDDL()).append(')'); return sb.toString(); } if (sFunction.equals(name)) { ddlName = StringConverter.toQuotedString(name, '"', true); } sb.append(ddlName).append('('); for (int i = iSqlArgStart; i < eArg.length; i++) { sb.append(eArg[i].getDDL()); if (i < eArg.length - 1) { sb.append(','); } } sb.append(')'); return sb.toString(); } /** * Returns a String representation of this object. <p> */ public String describe(Session session) { StringBuffer sb = new StringBuffer(); sb.append(super.toString()).append("=[\n"); sb.append(sFunction).append("("); for (int i = iSqlArgStart; i < eArg.length; i++) { sb.append("[").append(eArg[i].describe(session)).append("]"); } sb.append(") returns ").append(Types.getTypeString(getReturnType())); sb.append("]\n"); return sb.toString(); } /** * Returns the Java Class of the object returned by getValue(). <p> */ String getReturnClassName() { return returnClassName; } /** * Returns the Java Class of the i'th argument. <p> */ Class getArgClass(int i) { return aArgClasses[i]; } /** * Returns the SQL nullability code of the i'th argument. <p> */ int getArgNullability(int i) { return bArgNullable[i] ? Expression.NULLABLE : Expression.NO_NULLS; } Method getMethod() { return mMethod; } }