/* Copyright (c) 2001-2010, 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. */ /* * $Id: PgType.java 3481 2010-02-26 18:05:06Z fredt $ */ package org.hsqldb.server; import org.hsqldb.types.Type; import org.hsqldb.types.Types; import org.hsqldb.types.NumberType; import org.hsqldb.types.BooleanType; import org.hsqldb.types.CharacterType; import org.hsqldb.types.DateTimeType; import org.hsqldb.Session; import org.hsqldb.types.Types; import java.sql.SQLException; import java.io.Serializable; import org.hsqldb.types.JavaObjectData; import org.hsqldb.HsqlException; import org.hsqldb.types.BinaryData; import org.hsqldb.error.Error; import org.hsqldb.error.ErrorCode; import org.hsqldb.jdbc.Util; public class PgType { private int oid; private int typeWidth = -1; private int lpConstraint = -1; // Length or Precision private Type hType; public int getOid() { return oid; } public int getTypeWidth() { return typeWidth; } public int getLPConstraint() { return lpConstraint; } /** * Convenience wrapper for PgType constructor, when there is no * type width, length, or precision setting for the type. * * @see #PgType(Type, int, Integer, Integer) */ protected PgType(Type hType, int oid) { this(hType, oid, null, null); } /** * Convenience wrapper for PgType constructor, when there is no * length or precision setting for the type. * * @see #PgType(Type, int, Integer, Integer) */ protected PgType(Type hType, int oid, int typeWidth) { this(hType, oid, new Integer(typeWidth), null); } /** * Convenience wrapper for PgType constructor, when there is no fixed * width for the type. * * @param dummy Normally pass null. This is a dummy parameter just to make * a unique method signature. If non-null, will be treated * exactly the same as the typeWidthObject from the 3-param * constructor. * @see #PgType(Type, int, Integer, Integer) */ protected PgType(Type hType, int oid, Integer dummy, long lpConstraint) throws RecoverableOdbcFailure { this(hType, oid, dummy, new Integer((int) lpConstraint)); if (lpConstraint < 0) { throw new RecoverableOdbcFailure( "Length/Precision value is below minimum value of 0"); } if (lpConstraint > Integer.MAX_VALUE) { throw new RecoverableOdbcFailure( "Length/Precision value is above maximum value of " + Integer.MAX_VALUE); } } /** * @param hType HyperSQL data type * @param oid Numeric Object ID for the driver-side type. * @param typeWidthObject Fixed width for the type * @param lpConstraintObject Either length or Precision setting for this * instance of the type. * <b>IMPORTANT!</b> for all types with positive * lpConstraint other than Timestamps and Times, * add an extra 4 to satisy crazy driver protocol. */ protected PgType(Type hType, int oid, Integer typeWidthObject, Integer lpConstraintObject) { this.hType = hType; this.oid = oid; this.typeWidth = (typeWidthObject == null) ? -1 : typeWidthObject.intValue(); this.lpConstraint = (lpConstraintObject == null) ? -1 : lpConstraintObject.intValue(); } static public PgType getPgType(Type hType, boolean directColumn) throws RecoverableOdbcFailure { switch (hType.typeCode) { case Types.TINYINT: return tinyIntSingleton; case Types.SQL_SMALLINT: return int2singleton; case Types.SQL_INTEGER: return int4singleton; case Types.SQL_BIGINT: return int8singleton; case Types.SQL_NUMERIC: case Types.SQL_DECIMAL: return new PgType(hType, TYPE_NUMERIC, null, (hType.precision << 16) + hType.scale + 4); case Types.SQL_FLOAT: // TODO: // Improve the driver to make use of the Float precision // return new PgType(hType, TYPE_FLOAT8, null, hType.precision); case Types.SQL_DOUBLE: case Types.SQL_REAL: return doubleSingleton; case Types.BOOLEAN: return boolSingleton; case Types.SQL_CHAR: // = CHARACTER if (directColumn) { return new PgType(hType, TYPE_BPCHAR, null, hType.precision + 4); } return unknownSingleton; // constant value case Types.SQL_VARCHAR: // = CHARACTER VARYING = LONGVARCHAR case Types.VARCHAR_IGNORECASE: // Don't know if possible here if (hType.precision < 0) { throw new RecoverableOdbcFailure ( "Length/Precision value is below minimum value of 0"); } if (hType.precision > Integer.MAX_VALUE) { throw new RecoverableOdbcFailure ( "Length/Precision value is above maximum value of " + Integer.MAX_VALUE); } return (hType.precision != 0 && directColumn) ? new PgType(hType, TYPE_VARCHAR, null, hType.precision + 4) : textSingleton; // Return TEXT type for both unlimited VARCHARs, and for // Non-direct-table-col results. case Types.SQL_CLOB: // = CHARACTER LARGE OBJECT throw new RecoverableOdbcFailure ( "Driver doesn't support type 'CLOB' yet"); case Types.SQL_BLOB: // = BINARY LARGE OBJECT return new PgType(hType, TYPE_BLOB, null, hType.precision); case Types.SQL_BINARY: case Types.SQL_VARBINARY: // = BINARY VARYING return new PgType(hType, TYPE_BYTEA, null, hType.precision); // Note that we are returning SQL_BINARY data as if they were // variable. I don't think the unnecessary variability will // have any side-effects. // No reason to differentiate here, since the client's // atttypm parameter is where we would communicate the length // in both cases. case Types.OTHER: throw new RecoverableOdbcFailure ( "Driver doesn't support type 'OTHER' yet"); case Types.SQL_BIT: return bitSingleton; case Types.SQL_BIT_VARYING: return bitVaryingSingleton; // I have no idea why length contstaint spec is not needed for // BIT_VARYING. case Types.SQL_DATE: return dateSingleton; // 4 bytes case Types.SQL_TIME : return new PgType(hType, TYPE_TIME, new Integer(8), hType.precision); case Types.SQL_TIME_WITH_TIME_ZONE : return new PgType(hType, TYPE_TIME_WITH_TMZONE, new Integer(12), hType.precision); case Types.SQL_TIMESTAMP : return new PgType(hType, TYPE_TIMESTAMP_NO_TMZONE, new Integer(8), hType.precision); case Types.SQL_TIMESTAMP_WITH_TIME_ZONE : return new PgType(hType, TYPE_TIMESTAMP, new Integer(8), hType.precision); // Postgresql is returning type DATETIME for this case. // It should return TYPE_TIMESTAMP, no? /* ********************************************************* * For INTERVALs, we get the more specific type here, not just * SQL_INTERVAL. case Types.SQL_INTERVAL: * * The reason no precisions are passed to the ODBC client is that I * have so far been unsuccessful at figuring out exactly how the * driver wants the atttypmod formatted. See doc/odbc.txt for * notes about this. */ case Types.SQL_INTERVAL_YEAR: case Types.SQL_INTERVAL_YEAR_TO_MONTH: case Types.SQL_INTERVAL_MONTH: // Need to test these, since the driver Interval type is // intended for second-resolution only, not month resolution. throw new RecoverableOdbcFailure ( "Driver doesn't support month-resolution 'INTERVAL's yet"); case Types.SQL_INTERVAL_DAY: case Types.SQL_INTERVAL_DAY_TO_HOUR: case Types.SQL_INTERVAL_DAY_TO_MINUTE: case Types.SQL_INTERVAL_HOUR: case Types.SQL_INTERVAL_HOUR_TO_MINUTE: case Types.SQL_INTERVAL_MINUTE: // Our server uses the type to distinguish the resolution here. // The driver expects these types to be distinguished in the // value itself, like "99 days". // Therefore, these types are incompatible until driver is // enhanced. throw new RecoverableOdbcFailure ( "Driver doesn't support non-second-resolution 'INTERVAL's " + "yet"); case Types.SQL_INTERVAL_DAY_TO_SECOND: PgType.ignoredConstraintWarning(hType); return daySecIntervalSingleton; case Types.SQL_INTERVAL_HOUR_TO_SECOND: PgType.ignoredConstraintWarning(hType); return hourSecIntervalSingleton; case Types.SQL_INTERVAL_MINUTE_TO_SECOND: PgType.ignoredConstraintWarning(hType); return minSecIntervalSingleton; case Types.SQL_INTERVAL_SECOND: PgType.ignoredConstraintWarning(hType); return secIntervalSingleton; default: throw new RecoverableOdbcFailure ( "Unsupported type: " + hType.getNameString()); } } /** * This method copied from JDBCPreparedStatement.java. * * The internal parameter value setter always converts the parameter to * the Java type required for data transmission. * <P> * This method will not be called for binary types. Binary values are * just loaded directly into the Object parameter array. * </P> * * @throws SQLException if either argument is not acceptable. */ public Object getParameter(String inString, Session session) throws SQLException, RecoverableOdbcFailure { if (inString == null) { return null; } Object o = inString; switch (hType.typeCode) { case Types.SQL_BOOLEAN : if (inString.length() == 1) switch (inString.charAt(0)) { case 'T': case 't': case 'Y': case 'y': case '1': return Boolean.TRUE; default: return Boolean.FALSE; } return Boolean.valueOf(inString); case Types.SQL_BINARY : case Types.SQL_VARBINARY : case Types.SQL_BLOB : throw new RecoverableOdbcFailure( "This data type should be transmitted to server in binary " + "format: " + hType.getNameString()); case Types.OTHER : case Types.SQL_CLOB : throw new RecoverableOdbcFailure( "Type not supported yet: " + hType.getNameString()); /* case Types.OTHER : try { if (o instanceof Serializable) { o = new JavaObjectData((Serializable) o); break; } } catch (HsqlException e) { PgType.throwError(e); } PgType.throwError(Error.error(ErrorCode.X_42565)); break; case Types.SQL_BLOB : //setBlobParameter(i, o); //break; case Types.SQL_CLOB : //setClobParameter(i, o); //break; */ case Types.SQL_DATE : case Types.SQL_TIME_WITH_TIME_ZONE : case Types.SQL_TIMESTAMP_WITH_TIME_ZONE : case Types.SQL_TIME : case Types.SQL_TIMESTAMP : { try { o = hType.convertToType(session, o, Type.SQL_VARCHAR); } catch (HsqlException e) { PgType.throwError(e); } break; } case Types.TINYINT : case Types.SQL_SMALLINT : case Types.SQL_INTEGER : case Types.SQL_BIGINT : case Types.SQL_REAL : case Types.SQL_FLOAT : case Types.SQL_DOUBLE : case Types.SQL_NUMERIC : case Types.SQL_DECIMAL : try { o = hType.convertToType(session, o, Type.SQL_VARCHAR); } catch (HsqlException e) { PgType.throwError(e); } break; default : /* throw new RecoverableOdbcFailure( "Parameter value is of unexpected type: " + hType.getNameString()); */ try { o = hType.convertToDefaultType(session, o); // Supposed to handle String -> SQL_BIT. Not working. } catch (HsqlException e) { PgType.throwError(e); } break; } return o; } public String valueString(Object datum) { String dataString = hType.convertToString(datum); switch (hType.typeCode) { case Types.SQL_BOOLEAN : return String.valueOf(((Boolean) datum).booleanValue() ? 't' : 'f'); // Default would probably work fine, since the Driver looks at // only the first byte, but this why send an extra 3 or 4 bytes // with every data, plus there could be some dependency upon // single-character in the driver code somewhere. case Types.SQL_VARBINARY : case Types.SQL_BINARY : dataString = OdbcUtil.hexCharsToOctalOctets(dataString); break; } return dataString; } /* * The followign settings are a Java port of pgtypes.h */ public static final int TYPE_BOOL = 16; public static final int TYPE_BYTEA = 17; public static final int TYPE_CHAR = 18; public static final int TYPE_NAME = 19; public static final int TYPE_INT8 = 20; public static final int TYPE_INT2 = 21; public static final int TYPE_INT2VECTOR = 22; public static final int TYPE_INT4 = 23; public static final int TYPE_REGPROC = 24; public static final int TYPE_TEXT = 25; public static final int TYPE_OID = 26; public static final int TYPE_TID = 27; public static final int TYPE_XID = 28; public static final int TYPE_CID = 29; public static final int TYPE_OIDVECTOR = 30; public static final int TYPE_SET = 32; public static final int TYPE_XML = 142; public static final int TYPE_XMLARRAY = 143; public static final int TYPE_CHAR2 = 409; public static final int TYPE_CHAR4 = 410; public static final int TYPE_CHAR8 = 411; public static final int TYPE_POINT = 600; public static final int TYPE_LSEG = 601; public static final int TYPE_PATH = 602; public static final int TYPE_BOX = 603; public static final int TYPE_POLYGON = 604; public static final int TYPE_FILENAME = 605; public static final int TYPE_CIDR = 650; public static final int TYPE_FLOAT4 = 700; public static final int TYPE_FLOAT8 = 701; public static final int TYPE_ABSTIME = 702; public static final int TYPE_RELTIME = 703; public static final int TYPE_TINTERVAL = 704; public static final int TYPE_UNKNOWN = 705; public static final int TYPE_MONEY = 790; public static final int TYPE_OIDINT2 = 810; public static final int TYPE_MACADDR = 829; public static final int TYPE_INET = 869; public static final int TYPE_OIDINT4 = 910; public static final int TYPE_OIDNAME = 911; public static final int TYPE_TEXTARRAY = 1009; public static final int TYPE_BPCHARARRAY = 1014; public static final int TYPE_VARCHARARRAY = 1015; public static final int TYPE_BPCHAR = 1042; public static final int TYPE_VARCHAR = 1043; public static final int TYPE_DATE = 1082; public static final int TYPE_TIME = 1083; public static final int TYPE_TIMESTAMP_NO_TMZONE = 1114; /* since 7.2 */ public static final int TYPE_DATETIME = 1184; public static final int TYPE_TIME_WITH_TMZONE = 1266; /* since 7.1 */ public static final int TYPE_TIMESTAMP = 1296; /* deprecated since 7.0 */ public static final int TYPE_NUMERIC = 1700; public static final int TYPE_RECORD = 2249; public static final int TYPE_VOID = 2278; public static final int TYPE_UUID = 2950; // Numbering new HyperSQL-only client-side types beginning with 9999 and // getting lower, to reduce chance of conflict with future PostreSQL types. public static final int TYPE_BLOB = 9998; public static final int TYPE_TINYINT = 9999; // Apparenly new additions, from Postgresql server file pg_type.h: public static final int TYPE_BIT = 1560; // Also defined is _bit. No idea what that is about public static final int TYPE_VARBIT = 1562; // Also defined is _varbit. No idea what that is about /* Following stuff is to support code copied from * JDBCPreparedStatement.java. */ static final void throwError(HsqlException e) throws SQLException { //#ifdef JAVA6 throw Util.sqlException(e.getMessage(), e.getSQLState(), e.getErrorCode(), e); //#else /* throw new SQLException(e.getMessage(), e.getSQLState(), e.getErrorCode()); */ //#endif JAVA6 } static protected final PgType tinyIntSingleton = new PgType(Type.TINYINT, TYPE_TINYINT, 1); static protected final PgType int2singleton = new PgType(Type.SQL_SMALLINT, TYPE_INT2, 2); static protected final PgType int4singleton = new PgType(Type.SQL_INTEGER, TYPE_INT4, 4); static protected final PgType int8singleton = new PgType(Type.SQL_BIGINT, TYPE_INT8, 8); static protected final PgType doubleSingleton = new PgType(Type.SQL_DOUBLE, TYPE_FLOAT8, 8); static protected final PgType boolSingleton = new PgType(Type.SQL_BOOLEAN, TYPE_BOOL, 1); static protected final PgType textSingleton = new PgType(Type.SQL_VARCHAR, TYPE_TEXT); static protected final PgType dateSingleton = new PgType(Type.SQL_DATE, TYPE_DATE, 4); static protected final PgType unknownSingleton = new PgType(Type.SQL_CHAR_DEFAULT, TYPE_UNKNOWN, -2); static protected final PgType bitSingleton = new PgType(Type.SQL_BIT, TYPE_BIT); static protected final PgType bitVaryingSingleton = new PgType(Type.SQL_BIT_VARYING, TYPE_VARBIT); static protected final PgType daySecIntervalSingleton = new PgType(Type.SQL_INTERVAL_DAY_TO_SECOND, TYPE_TINTERVAL, 16); static protected final PgType hourSecIntervalSingleton = new PgType(Type.SQL_INTERVAL_HOUR_TO_SECOND, TYPE_TINTERVAL, 16); static protected final PgType minSecIntervalSingleton = new PgType(Type.SQL_INTERVAL_MINUTE_TO_SECOND, TYPE_TINTERVAL, 16); static protected final PgType secIntervalSingleton = new PgType(Type.SQL_INTERVAL_SECOND, TYPE_TINTERVAL, 16); static private void ignoredConstraintWarning(Type hsqldbType) { if (hsqldbType.precision == 0 && hsqldbType.scale == 0) { return; } // TODO: Use logging system! /* System.err.println( "WARNING: Not passing INTERVAL precision setting " + "or second precision setting to ODBC client"); */ } }