/* Copyright (c) 2001-2009, 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.dbinfo; import java.io.Serializable; import java.lang.reflect.Method; import org.hsqldb.StatementDMQL; import org.hsqldb.Types; import org.hsqldb.lib.HashMap; import org.hsqldb.resources.BundleHandler; import org.hsqldb.store.ValuePool; import org.hsqldb.types.BinaryData; import org.hsqldb.types.Type; /* $Id: DIProcedureInfo.java 3001 2009-06-04 12:31:11Z fredt $ */ // boucherb@users 20051207 - patch 1.8.0.x initial JDBC 4.0 support work // Revision 1.7 2006/07/12 11:27:53 boucherb // patch 1.9.0 // - JDBC 4.0, Mustang b87: spec. changed to unquoted values for IS_NULLABLE procedure and function column metadata /** * Provides information about HSQLDB SQL-invoked routines and SQL functions. <p> * * In particular, this class provides information about Java Methods in a form * compatible with presentation via the related HSQLDB system tables, * SYSTEM_PROCEDURES and SYSTEM_PROCEDURECOLUMNS, involved in the production of * the DatabaseMetaData getProcedures and getProcedureColumns result sets. * * @author boucherb@users * @version 1.9.0 * @since 1.7.2 */ final class DIProcedureInfo { // java.sql dependencies mostly removed static final String conClsName = "java.sql.Connection"; static final int procedureResultUnknown = 0; static final int procedureNoResult = 1; static final int procedureReturnsResult = 2; static final int procedureColumnUnknown = 0; static final int procedureColumnIn = 1; static final int procedureColumnInOut = 2; static final int procedureColumnResult = 3; static final int procedureColumnOut = 4; static final int procedureColumnReturn = 5; static final int procedureNoNulls = 0; static final int procedureNullable = 1; static final int procedureNullableUnknown = 2; private Class clazz; private Class[] colClasses; private int[] colTypes; private int colOffset; private int colCount; private boolean colsResolved; private String fqn; private String specificName; private int hnd_remarks; private Method method; private String sig; private DINameSpace nameSpace; private final HashMap typeMap = new HashMap(); public DIProcedureInfo(DINameSpace ns) { setNameSpace(ns); } private int colOffset() { if (!colsResolved) { resolveCols(); } return colOffset; } /* HsqlArrayList getAliases() { return (HsqlArrayList) nameSpace.getInverseAliasMap().get(getFQN()); } */ Class getColClass(int i) { return colClasses[i + colOffset()]; } int getColCount() { if (!colsResolved) { resolveCols(); } return colCount; } Integer getColJDBCDataType(int i) { return ValuePool.getInt(Type.getJDBCTypeCode(getColTypeCode(i))); } Integer getColLen(int i) { int size; int type; type = getColTypeCode(i); switch (type) { case Types.SQL_BINARY : case Types.SQL_LONGVARBINARY : case Types.SQL_VARBINARY : { size = Integer.MAX_VALUE; break; } case Types.SQL_BIGINT : case Types.SQL_DOUBLE : case Types.SQL_DATE : case Types.SQL_FLOAT : case Types.SQL_TIME_WITH_TIME_ZONE : case Types.SQL_TIME : { size = 8; break; } case Types.SQL_TIMESTAMP_WITH_TIME_ZONE : case Types.SQL_TIMESTAMP : { size = 12; break; } case Types.SQL_REAL : case Types.SQL_INTEGER : { size = 4; break; } case Types.SQL_SMALLINT : { size = 2; break; } case Types.TINYINT : case Types.SQL_BOOLEAN : { size = 1; break; } default : size = 0; } return (size == 0) ? null : ValuePool.getInt(size); } String getColName(int i) { return StatementDMQL.PCOL_PREFIX + (i + colOffset()); } Integer getColNullability(int i) { int cn; cn = getColClass(i).isPrimitive() ? procedureNoNulls : procedureNullable; return ValuePool.getInt(cn); } // JDBC 4.0 // - revised Mustang b87 : no quotes String getColIsNullable(int i) { return getColClass(i).isPrimitive() ? "NO" : "YES"; } String getColRemark(int i) { String key; StringBuffer sb; sb = new StringBuffer(getSignature()); key = sb.append('@').append(i + colOffset()).toString(); return BundleHandler.getString(hnd_remarks, key); } // JDBC sort-contract: // out return value column, then in/in out/out parameter columns // in formal order, then result columns in column order // // Currently, we materialize the java method return value, if // any, as a result column, not as an OUT return value column, so // it should actually appear _after_ the other procedure columns // in the row order returned by the JDBC getProcedureColumns() method int getColSequence(int i) { // colOffset has the side-effect of setting colCount properly return (i + colOffset() == 0) ? colCount : i; } // JDBC 4.0 // The ordinal position, starting from 1, for the // input and output parameters for a procedure. // A value of 0 is returned if this row describes // the procedure's return value. Integer getColOrdinalPosition(int i) { return ValuePool.getInt(colOffset() == 0 ? i : i + 1); } int getColTypeCode(int i) { i += colOffset(); return colTypes[i]; } Integer getColUsage(int i) { switch (i + colOffset()) { case 0 : { // Currently, we materialize the java method return value, if // any, as a result column, not as an OUT return column return ValuePool.getInt(procedureColumnResult); } /** * @todo - registration and reporting on result columns for routines * that generate real" result sets */ default : { // We could get religious here and maybe report IN OUT // for newRow of before update for each row trigger methods, // but there's not really any added value return ValuePool.getInt(procedureColumnIn); } } } Class getDeclaringClass() { return this.clazz; } String getFQN() { StringBuffer sb; if (fqn == null) { sb = new StringBuffer(); fqn = sb.append(clazz.getName()).append('.').append( method.getName()).toString(); } return fqn; } String getSpecificName() { if (specificName == null) { specificName = clazz.getName() + "." + getSignature(); } return specificName; } Integer getInputParmCount() { return ValuePool.getInt(method.getParameterTypes().length); } Method getMethod() { return this.method; } String getOrigin(String srcType) { return (nameSpace.isBuiltin(clazz) ? "BUILTIN " : "USER DEFINED ") + srcType; } Integer getOutputParmCount() { // no support for IN OUT or OUT columns yet return ValuePool.INTEGER_0; } String getRemark() { return BundleHandler.getString(hnd_remarks, getSignature()); } Integer getResultSetCount() { return (method.getReturnType() == Void.TYPE) ? ValuePool.INTEGER_0 : ValuePool.INTEGER_1; } Integer getResultType(String origin) { int type; type = !"ROUTINE".equals(origin) ? procedureResultUnknown : method.getReturnType() == Void.TYPE ? procedureNoResult : procedureReturnsResult; return ValuePool.getInt(type); } String getSignature() { if (sig == null) { sig = DINameSpace.getSignature(method); } return sig; } /** * Retrieves the specific name of the given Method object. <p> * * @param m The Method object for which to retreive the specific name * @return the specific name of the specified Method object. */ static String getMethodSpecificName(Method m) { return m == null ? null : m.getDeclaringClass().getName() + '.' + DINameSpace.getSignature(m); } DINameSpace getNameSpace() { return nameSpace; } void setNameSpace(DINameSpace ns) { nameSpace = ns; Class c; Integer type; // can only speed up test significantly for java.lang.Object, // final classes, primitive types and hierachy parents. // Must still check later if assignable from candidate classes, where // hierarchy parent is not final. //ARRAY try { c = nameSpace.classForName("org.hsqldb.jdbc.JDBCArray"); typeMap.put(c, ValuePool.getInt(Types.SQL_ARRAY)); } catch (Exception e) {} // BIGINT type = ValuePool.getInt(Types.SQL_BIGINT); typeMap.put(Long.TYPE, type); typeMap.put(Long.class, type); // BOOLEAN type = ValuePool.getInt(Types.SQL_BOOLEAN); typeMap.put(Boolean.TYPE, type); typeMap.put(Boolean.class, type); // BLOB type = ValuePool.getInt(Types.SQL_BLOB); try { c = nameSpace.classForName("org.hsqldb.jdbc.JDBCBlob"); typeMap.put(c, type); } catch (Exception e) {} // CHAR type = ValuePool.getInt(Types.SQL_CHAR); typeMap.put(Character.TYPE, type); typeMap.put(Character.class, type); typeMap.put(Character[].class, type); typeMap.put(char[].class, type); // CLOB type = ValuePool.getInt(Types.SQL_CLOB); try { c = nameSpace.classForName("org.hsqldb.jdbc.JDBCClob"); typeMap.put(c, type); } catch (Exception e) {} // DATALINK type = ValuePool.getInt(Types.SQL_DATALINK); typeMap.put(java.net.URL.class, type); // DATE type = ValuePool.getInt(Types.SQL_DATE); typeMap.put(java.util.Date.class, type); typeMap.put(java.sql.Date.class, type); // DECIMAL type = ValuePool.getInt(Types.SQL_DECIMAL); try { c = nameSpace.classForName("java.math.BigDecimal"); typeMap.put(c, type); } catch (Exception e) {} // DISTINCT try { c = nameSpace.classForName("org.hsqldb.jdbc.JDBCDistinct"); typeMap.put(c, ValuePool.getInt(Types.DISTINCT)); } catch (Exception e) {} // DOUBLE type = ValuePool.getInt(Types.SQL_DOUBLE); typeMap.put(Double.TYPE, type); typeMap.put(Double.class, type); // FLOAT : Not actually a legal IN parameter type yet type = ValuePool.getInt(Types.SQL_FLOAT); typeMap.put(Float.TYPE, type); typeMap.put(Float.class, type); // INTEGER type = ValuePool.getInt(Types.SQL_INTEGER); typeMap.put(Integer.TYPE, type); typeMap.put(Integer.class, type); // JAVA_OBJECT type = ValuePool.getInt(Types.JAVA_OBJECT); typeMap.put(Object.class, type); // VARBINARY type = ValuePool.getInt(Types.SQL_VARBINARY); typeMap.put(byte[].class, type); typeMap.put(BinaryData.class, type); // VARCHAR type = ValuePool.getInt(Types.SQL_VARCHAR); typeMap.put(String.class, type); // NULL type = ValuePool.getInt(Types.SQL_ALL_TYPES); typeMap.put(Void.TYPE, type); typeMap.put(Void.class, type); // REF type = ValuePool.getInt(Types.SQL_REF); try { c = nameSpace.classForName("org.hsqldb.jdbc.JDBCRef"); typeMap.put(c, type); } catch (Exception e) {} // SMALLINT : Not actually a legal IN parameter type yet type = ValuePool.getInt(Types.SQL_SMALLINT); typeMap.put(Short.TYPE, type); typeMap.put(Short.class, type); // STRUCT : type = ValuePool.getInt(Types.STRUCT); try { c = nameSpace.classForName("org.hsqldb.jdbc.JDBCStruct"); typeMap.put(c, type); } catch (Exception e) {} // TIME type = ValuePool.getInt(Types.SQL_TIME); typeMap.put(java.sql.Time.class, type); // TIMESTAMP type = ValuePool.getInt(Types.SQL_TIMESTAMP); typeMap.put(java.sql.Timestamp.class, type); // TINYINT : Not actually a legal IN parameter type yet type = ValuePool.getInt(Types.TINYINT); typeMap.put(Byte.TYPE, type); typeMap.put(Byte.class, type); // XML : Not actually a legal IN parameter type yet type = ValuePool.getInt(Types.SQL_XML); try { c = nameSpace.classForName("org.w3c.dom.Document"); typeMap.put(c, type); c = nameSpace.classForName("org.w3c.dom.DocumentFragment"); typeMap.put(c, type); } catch (Exception e) {} } private void resolveCols() { Class rType; Class[] pTypes; Class clazz; int ptlen; int pclen; boolean isFPCON; rType = method.getReturnType(); pTypes = method.getParameterTypes(); ptlen = pTypes.length; isFPCON = ptlen > 0 && pTypes[0].getName().equals(conClsName); pclen = 1 + ptlen - (isFPCON ? 1 : 0); colClasses = new Class[pclen]; colTypes = new int[pclen]; colClasses[0] = rType; colTypes[0] = typeForClass(rType); for (int i = isFPCON ? 1 : 0, idx = 1; i < ptlen; i++, idx++) { clazz = pTypes[i]; colClasses[idx] = clazz; colTypes[idx] = typeForClass(clazz); } colOffset = rType == Void.TYPE ? 1 : 0; colCount = pclen - colOffset; } /** * This requires the following properties files: * * org_hsqldb_Library.properties * java_math.properties */ void setMethod(Method m) { String remarkKey; method = m; clazz = method.getDeclaringClass(); fqn = null; specificName = null; sig = null; colsResolved = false; remarkKey = clazz.getName().replace('.', '_'); hnd_remarks = BundleHandler.getBundleHandle(remarkKey, null); } int typeForClass(Class c) { Class to; Integer type = (Integer) typeMap.get(c); if (type != null) { return type.intValue(); } // ARRAY (dimension 1) // HSQLDB does not yet support ARRAY for SQL, but // Trigger.fire takes Object[] row, which we report. // Also, it's just friendly to show what "would" // be required if/when we support ARRAY in a broader // sense if (c.isArray() && !c.getComponentType().isArray()) { return Types.SQL_ARRAY; } try { to = Class.forName("java.sql.Array"); if (to.isAssignableFrom(c)) { return Types.SQL_ARRAY; } } catch (Exception e) {} // NUMERIC // All java.lang.Number impls and BigDecimal have // already been covered by lookup in typeMap. // They are all final, so this is OK. if (Number.class.isAssignableFrom(c)) { return Types.SQL_NUMERIC; } // TIMESTAMP try { to = Class.forName("java.sql.Timestamp"); if (to.isAssignableFrom(c)) { return Types.SQL_TIMESTAMP; } } catch (Exception e) {} // TIME try { to = Class.forName("java.sql.Time"); if (to.isAssignableFrom(c)) { return Types.SQL_TIME; } } catch (Exception e) {} // DATE try { to = Class.forName("java.sql.Date"); if (to.isAssignableFrom(c)) { return Types.SQL_DATE; } } catch (Exception e) {} // BLOB try { to = Class.forName("java.sql.Blob"); if (to.isAssignableFrom(c)) { return Types.SQL_BLOB; } } catch (Exception e) {} // CLOB try { to = Class.forName("java.sql.Clob"); if (to.isAssignableFrom(c)) { return Types.SQL_CLOB; } } catch (Exception e) {} // REF try { to = Class.forName("java.sql.Ref"); if (to.isAssignableFrom(c)) { return Types.SQL_REF; } } catch (Exception e) {} // STRUCT try { to = Class.forName("java.sql.Struct"); if (to.isAssignableFrom(c)) { return Types.STRUCT; } } catch (Exception e) {} // VARBINARY : org.hsqldb.Binary is not final if (BinaryData.class.isAssignableFrom(c)) { return Types.SQL_VARBINARY; } // VARCHAR : really OTHER at this point try { // @since JDK1.4 to = Class.forName("java.lang.CharSequence"); if (to.isAssignableFrom(c)) { return Types.SQL_VARCHAR; } } catch (Exception e) {} // we have no standard mapping for the specified class // at this point...is it even storable? if (Serializable.class.isAssignableFrom(c)) { // Yes: it is storable, as an OTHER. return Types.OTHER; } // It may (in future, say using bean contract) or may not be storable // (by HSQLDB)... // but then it may be possible to pass to an in-process routine, // so be lenient and just return the most generic type. return Types.JAVA_OBJECT; } }