/* 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.io.IOException; //import java.io.Serializable; import j2me.math.BigDecimal; import j2me.math.BigInteger; import j2me.math.Number; import j2me.sql.Date; import j2me.sql.Time; import j2me.sql.Timestamp; import org.hsqldb.HsqlNameManager.HsqlName; import org.hsqldb.lib.StringConverter; import org.hsqldb.store.ValuePool; import org.hsqldb.types.Binary; import org.hsqldb.types.JavaObject; import org.hsqldb.lib.java.JavaSystem; // fredt@users 20020130 - patch 491987 by jimbag@users // fredt@users 20020320 - doc 1.7.0 - update // fredt@users 20020401 - patch 442993 by fredt - arithmetic expressions // to allow mixed type arithmetic expressions beginning with a narrower type // changes applied to several lines of code and not marked separately // consists of changes to arithmatic functions to allow promotion of // java.lang.Number values and new functions to choose type for promotion // fredt@users 20020401 - patch 455757 by galena@users (Michiel de Roo) // interpretation of TINYINT as Byte instead of Short // fredt@users 20020130 - patch 491987 by jimbag@users // support for sql standard char and varchar. size is maintained as // defined in the DDL and trimming and padding takes place accordingly // modified by fredt - trimming and padding are turned off by default but // can be applied accross the database by defining sql.enforce_size=true in // database.properties file // fredt@users 20020215 - patch 1.7.0 by fredt - quoted identifiers // applied to different parts to support the sql standard for // naming of columns and tables (use of quoted identifiers as names) // fredt@users 20020328 - patch 1.7.0 by fredt - change REAL to Double // fredt@users 20020402 - patch 1.7.0 by fredt - type conversions // frequently used type conversions are done without creating temporary // Strings to reduce execution time and garbage collection // fredt@users 20021013 - patch 1.7.1 by fredt - type conversions // scripting of Double.Nan and infinity values // fredt@users 20030715 - patch 1.7.2 - type narrowing for numeric values // fredt@users - patch 1.8.0 - enforcement of precision and scale /** * Implementation of SQL table columns as defined in DDL statements with * static methods to process their values.
* * Enhanced type checking and conversion by fredt@users * * @author Thomas Mueller (Hypersonic SQL Group) * @author fredt@users * @version 1.8.0 * @since Hypersonic SQL */ public class Column { // -------------------------------------------------- // DDL name, size, scale, null, identity and default values // most variables are final but not declared so because of a bug in // JDK 1.1.8 compiler public HsqlName columnName; private int colType; private int colSize; private int colScale; private boolean isNullable; private boolean isIdentity; private boolean isPrimaryKey; private Expression defaultExpression; long identityStart; long identityIncrement; static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE); static final BigInteger MAX_INT = BigInteger.valueOf(Integer.MAX_VALUE); static final BigInteger MIN_INT = BigInteger.valueOf(Integer.MIN_VALUE); static final BigDecimal BIG_DECIMAL_0 = new BigDecimal(0.0); static final BigDecimal BIG_DECIMAL_1 = new BigDecimal(1.0); /** * Creates a column defined in DDL statement. * * @param name * @param nullable * @param type * @param size * @param scale * @param identity * @param startvalue * @param increment * @param primarykey * @param defstring */ Column(HsqlName name, boolean nullable, int type, int size, int scale, boolean primarykey, Expression defexpression) throws HsqlException { columnName = name; isNullable = nullable; colType = type; colSize = size; colScale = scale; isPrimaryKey = primarykey; defaultExpression = defexpression; } void setIdentity(boolean identity, long startvalue, long increment) throws HsqlException { isIdentity = identity; identityStart = startvalue; identityIncrement = increment; if (isIdentity) { if (colType == Types.INTEGER) { if (identityStart > Integer.MAX_VALUE || identityIncrement > Integer.MAX_VALUE) { throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE, columnName.statementName); } } } } private Column() {} /** * Used for primary key changes. */ Column duplicate(boolean withIdentity) throws HsqlException { Column newCol = new Column(); newCol.columnName = columnName; newCol.isNullable = isNullable; newCol.colType = colType; newCol.colSize = colSize; newCol.colScale = colScale; newCol.defaultExpression = defaultExpression; if (withIdentity) { newCol.setIdentity(isIdentity, identityStart, identityIncrement); } return newCol; } void setType(Column other) { isNullable = other.isNullable; colType = other.colType; colSize = other.colSize; colScale = other.colScale; } /** * Is this the identity column in the table. * * @return boolean */ boolean isIdentity() { return isIdentity; } /** * Is column nullable. * * @return boolean */ boolean isNullable() { return isNullable; } /** * Set nullable. * */ void setNullable(boolean value) { isNullable = value; } /** * Is this single column primary key of the table. * * @return boolean */ public boolean isPrimaryKey() { return isPrimaryKey; } /** * Set primary key. * */ void setPrimaryKey(boolean value) { isPrimaryKey = value; } /** * Returns default value in the session context. */ Object getDefaultValue(Session session) throws HsqlException { return defaultExpression == null ? null : defaultExpression.getValue(session, colType); } /** * Returns DDL for default value. */ String getDefaultDDL() { String ddl = null; try { ddl = defaultExpression == null ? null : defaultExpression.getDDL(); } catch (HsqlException e) {} return ddl; } /** * Returns default expression for the column. */ Expression getDefaultExpression() { return defaultExpression; } void setDefaultExpression(Expression expr) { defaultExpression = expr; } /** * Type of the column. * * @return java.sql.Types int value for the column */ int getType() { return colType; } int getDIType() { return colType == Types.VARCHAR_IGNORECASE ? Types.VARCHAR : colType; } int getDITypeSub() { if (colType == Types.VARCHAR_IGNORECASE) { return Types.TYPE_SUB_IGNORECASE; } return Types.TYPE_SUB_DEFAULT; } /** * Size of the column in DDL (0 if not defined). * * @return DDL size of column */ int getSize() { return colSize; } /** * Scale of the column in DDL (0 if not defined). * * @return DDL scale of column */ int getScale() { return colScale; } /** * Add two object of a given type * * @param a * @param b * @param type * @return result * @throws HsqlException */ static Object add(Object a, Object b, int type) throws HsqlException { if (a == null || b == null) { return null; } switch (type) { case Types.NULL : return null; case Types.REAL : case Types.FLOAT : case Types.DOUBLE : { double ad = Number.doubleValue(a); double bd = Number.doubleValue(b); return ValuePool.getDouble(Double.doubleToLongBits(ad + bd)); // return new Double(ad + bd); } case Types.VARCHAR : case Types.CHAR : case Types.LONGVARCHAR : case Types.VARCHAR_IGNORECASE : return (String) a + (String) b; case Types.NUMERIC : case Types.DECIMAL : { BigDecimal abd = (BigDecimal) a; BigDecimal bbd = (BigDecimal) b; return abd.add(bbd); } case Types.TINYINT : case Types.SMALLINT : case Types.INTEGER : { int ai = Number.intValue(a); int bi = Number.intValue(b); return ValuePool.getInt(ai + bi); } case Types.BIGINT : { long longa = Number.longValue(a); long longb = Number.longValue(b); return ValuePool.getLong(longa + longb); } default : throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED, Types.getTypeString(type)); } } /** * Concat two objects by turning them into strings first. * * @param a * @param b * @return result * @throws HsqlException */ static Object concat(Object a, Object b) throws HsqlException { if (a == null || b == null) { return null; } return a.toString() + b.toString(); } /** * Negate a numeric object. * * @param a * @param type * @return result * @throws HsqlException */ static Object negate(Object a, int type) throws HsqlException { if (a == null) { return null; } switch (type) { case Types.NULL : return null; case Types.REAL : case Types.FLOAT : case Types.DOUBLE : { double ad = -Number.doubleValue(a); return ValuePool.getDouble(Double.doubleToLongBits(ad)); } case Types.NUMERIC : case Types.DECIMAL : return ((BigDecimal) a).negate(); case Types.TINYINT : case Types.SMALLINT : case Types.INTEGER : return ValuePool.getInt(- Number.intValue(a)); case Types.BIGINT : return ValuePool.getLong(- Number.longValue(a)); default : throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED, Types.getTypeString(type)); } } /** * Multiply two numeric objects. * * @param a * @param b * @param type * @return result * @throws HsqlException */ static Object multiply(Object a, Object b, int type) throws HsqlException { if (a == null || b == null) { return null; } // fredt@users - type conversion - may need to apply to other arithmetic operations too if (!(Number.isNumber(a) && Number.isNumber(b))) { a = Column.convertObject(a, type); b = Column.convertObject(b, type); } switch (type) { case Types.NULL : return null; case Types.REAL : case Types.FLOAT : case Types.DOUBLE : { double ad = Number.doubleValue(a); double bd = Number.doubleValue(b); return ValuePool.getDouble(Double.doubleToLongBits(ad * bd)); } case Types.NUMERIC : case Types.DECIMAL : { BigDecimal abd = (BigDecimal) a; BigDecimal bbd = (BigDecimal) b; return abd.multiply(bbd); } case Types.TINYINT : case Types.SMALLINT : case Types.INTEGER : { int ai = Number.intValue(a); int bi = Number.intValue(b); return ValuePool.getInt(ai * bi); } case Types.BIGINT : { long longa = Number.longValue(a); long longb = Number.longValue(b); return ValuePool.getLong(longa * longb); } default : throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED, Types.getTypeString(type)); } } /** * Divide numeric object a by object b. * * @param a * @param b * @param type * @return result * @throws HsqlException */ static Object divide(Object a, Object b, int type) throws HsqlException { if (a == null || b == null) { return null; } switch (type) { case Types.NULL : return null; case Types.REAL : case Types.FLOAT : case Types.DOUBLE : { double ad = Number.doubleValue(a); double bd = Number.doubleValue(b); return ValuePool.getDouble(Double.doubleToLongBits(ad / bd)); } case Types.NUMERIC : case Types.DECIMAL : { BigDecimal abd = (BigDecimal) a; BigDecimal bbd = (BigDecimal) b; int scale = abd.scale() > bbd.scale() ? abd.scale() : bbd.scale(); return (bbd.signum() == 0) ? null : abd.divide(bbd, scale, BigDecimal.ROUND_HALF_DOWN); } case Types.TINYINT : case Types.SMALLINT : case Types.INTEGER : { int ai = Number.intValue(a); int bi = Number.intValue(a); if (bi == 0) { throw Trace.error(Trace.DIVISION_BY_ZERO); } return ValuePool.getInt(ai / bi); } case Types.BIGINT : { long longa = Number.longValue(a); long longb = Number.longValue(b); return (longb == 0) ? null : ValuePool.getLong(longa / longb); } default : throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED, Types.getTypeString(type)); } } /** * Subtract numeric object b from object a. * * @param a * @param b * @param type * @return result * @throws HsqlException */ static Object subtract(Object a, Object b, int type) throws HsqlException { if (a == null || b == null) { return null; } switch (type) { case Types.NULL : return null; case Types.REAL : case Types.FLOAT : case Types.DOUBLE : { double ad = Number.doubleValue(a); double bd = Number.doubleValue(b); return ValuePool.getDouble(Double.doubleToLongBits(ad - bd)); } case Types.NUMERIC : case Types.DECIMAL : { BigDecimal abd = (BigDecimal) a; BigDecimal bbd = (BigDecimal) b; return abd.subtract(bbd); } case Types.TINYINT : case Types.SMALLINT : case Types.INTEGER : { int ai = Number.intValue(a); int bi = Number.intValue(b); return ValuePool.getInt(ai - bi); } case Types.BIGINT : { long longa = Number.longValue(a); long longb = Number.longValue(b); return ValuePool.getLong(longa - longb); } default : throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED, Types.getTypeString(type)); } } // boucherb@users 2003-09-25 // TODO: Maybe use //#ifdef tag or reflective static method attribute // instantiation to take advantage of String.compareToIgnoreCase when // available (JDK 1.2 and greater) during ANT build. That or perhaps // consider using either local character-wise comparison or first converting // to lower case and then to upper case. Sun states that the JDK 1.2 introduced // String.compareToIngnorCase() comparison involves calling // Character.toLowerCase(Character.toUpperCase()) on compared characters, // to correctly handle some caveats concering using only the one operation or // the other outside the ascii character range. // fredt@users 20020130 - patch 418022 by deforest@users // use of rtrim() to mimic SQL92 behaviour /** * Compare a with b and return int value as result. * * @param a instance of Java wrapper, depending on type, but always same for a & b (can be null) * @param b instance of Java wrapper, depending on type, but always same for a & b (can be null) * @param type one of the java.sql.Types * @return result 1 if a>b, 0 if a=b, -1 if b>a * @throws HsqlException */ static int compare(Collation collation, Object a, Object b, int type) { int i = 0; if (a == b) { return 0; } // Current null handling here: null==null and smaller any value // Note, standard SQL null handling is handled by Expression.test() calling testNull() instead of this! // Attention, this is also used for grouping ('null' is one group) if (a == null) { return -1; } if (b == null) { return 1; } switch (type) { case Types.NULL : return 0; case Types.VARCHAR : case Types.LONGVARCHAR : return collation.compare((String) a, (String) b); case Types.CHAR : return collation.compare(Library.rtrim((String) a), Library.rtrim((String) b)); case Types.VARCHAR_IGNORECASE : return collation.compareIgnoreCase(((String) a), ((String) b)); case Types.TINYINT : case Types.SMALLINT : case Types.INTEGER : { int ai = Number.intValue(a); int bi = Number.intValue(b); return (ai > bi) ? 1 : (bi > ai ? -1 : 0); } case Types.BIGINT : { long longa = Number.longValue(a); long longb = Number.longValue(b); return (longa > longb) ? 1 : (longb > longa ? -1 : 0); } case Types.REAL : case Types.FLOAT : case Types.DOUBLE : { double ad = Number.doubleValue(a); double bd = Number.doubleValue(b); return (ad > bd) ? 1 : (bd > ad ? -1 : 0); } case Types.NUMERIC : case Types.DECIMAL : i = ((BigDecimal) a).compareTo((BigDecimal) b); break; case Types.DATE : return HsqlDateTime.compare((Date) a, (Date) b); case Types.TIME : return HsqlDateTime.compare((Time) a, (Time) b); case Types.TIMESTAMP : return HsqlDateTime.compare((Timestamp) a, (Timestamp) b); case Types.BOOLEAN : { boolean boola = ((Boolean) a).booleanValue(); boolean boolb = ((Boolean) b).booleanValue(); return (boola == boolb) ? 0 : (boolb ? -1 : 1); } case Types.BINARY : case Types.VARBINARY : case Types.LONGVARBINARY : if (a instanceof Binary && b instanceof Binary) { i = compareTo(((Binary) a).getBytes(), ((Binary) b).getBytes()); } break; case Types.OTHER : return 0; } return (i == 0) ? 0 : (i < 0 ? -1 : 1); } /** * Convert an object into a Java object that represents its SQL type.
* All internal type conversion operations start with
* this method. If a direct conversion doesn't take place, the object
* is converted into a string first and an attempt is made to convert
* the string into the target type.
*
* One objective of this mehod is to ensure the Object can be converted
* to the given SQL type. For example, a very large BIGINT
* value cannot be narrowed down to an INTEGER or SMALLINT.
*
* Type conversion performed by this method has gradually evolved in 1.7.2
* to allow narrowing of numeric types in all cases according to the SQL
* standard.
*
* Another new objective is to normalize DATETIME values.
*
* Objects set via JDBC PreparedStatement use this method to convert
* the data to the Java type that is required for custom serialization
* by the engine.
*
* @param o
* @param type
* @return result
* @throws HsqlException
*/
public static Object convertObject(Object o,
int type) throws HsqlException {
try {
if (o == null) {
return null;
}
switch (type) {
case Types.NULL :
return null;
case Types.TINYINT :
if (o instanceof java.lang.String) {
o = Library.trim((String) o, " ", true, true);
int val = Integer.parseInt((String) o);
o = ValuePool.getInt(val);
}
if (o instanceof java.lang.Integer) {
int temp = Number.intValue(o);
if (Byte.MAX_VALUE < temp || temp < Byte.MIN_VALUE) {
throw Trace.error(
Trace.NUMERIC_VALUE_OUT_OF_RANGE);
}
return o;
}
if (o instanceof java.lang.Long) {
long temp = Number.longValue(o);
if (Byte.MAX_VALUE < temp || temp < Byte.MIN_VALUE) {
throw Trace.error(
Trace.NUMERIC_VALUE_OUT_OF_RANGE);
}
return ValuePool.getInt(Number.intValue(o));
}
// fredt@users - direct conversion to optimise JDBC setObject(Byte)
if (o instanceof java.lang.Byte) {
return ValuePool.getInt(Number.intValue(o));
}
// fredt@users - returns to this method for range checking
if (Number.isNumber(o)/*o instanceof java.lang.Number*/) {
return convertObject(convertToInt(o), type);
}
if (o instanceof java.lang.Boolean) {
return ((Boolean) o).booleanValue()
? ValuePool.getInt(1)
: ValuePool.getInt(0);
}
break;
case Types.SMALLINT :
if (o instanceof java.lang.String) {
o = Library.trim((String) o, " ", true, true);
int val = Integer.parseInt((String) o);
o = ValuePool.getInt(val);
}
if (o instanceof java.lang.Integer) {
int temp = Number.intValue(o);
if (Short.MAX_VALUE < temp
|| temp < Short.MIN_VALUE) {
throw Trace.error(
Trace.NUMERIC_VALUE_OUT_OF_RANGE);
}
return o;
}
if (o instanceof java.lang.Long) {
long temp = Number.longValue(o);
if (Short.MAX_VALUE < temp
|| temp < Short.MIN_VALUE) {
throw Trace.error(
Trace.NUMERIC_VALUE_OUT_OF_RANGE);
}
return ValuePool.getInt( Number.intValue(o));
}
// fredt@users - direct conversion for JDBC setObject(Short), etc.
if (o instanceof Byte || o instanceof Short) {
return ValuePool.getInt( Number.intValue(o));
}
// fredt@users - returns to this method for range checking
if (Number.isNumber(o)) {
return convertObject(convertToInt(o), type);
}
if (o instanceof java.lang.Boolean) {
return ((Boolean) o).booleanValue()
? ValuePool.getInt(1)
: ValuePool.getInt(0);
}
break;
case Types.INTEGER :
if (o instanceof java.lang.Integer) {
return o;
}
if (o instanceof java.lang.String) {
o = Library.trim((String) o, " ", true, true);
int val = Integer.parseInt((String) o);
return ValuePool.getInt(val);
}
if (o instanceof java.lang.Long) {
long temp = Number.longValue(o);
if (Integer.MAX_VALUE < temp
|| temp < Integer.MIN_VALUE) {
throw Trace.error(
Trace.NUMERIC_VALUE_OUT_OF_RANGE);
}
return ValuePool.getInt(Number.intValue(o));
}
if (o instanceof Byte || o instanceof Short) {
return ValuePool.getInt(Number.intValue(o));
}
if (Number.isNumber(o)/*o instanceof java.lang.Number*/) {
return convertToInt(o);
}
if (o instanceof java.lang.Boolean) {
return ((Boolean) o).booleanValue()
? ValuePool.getInt(1)
: ValuePool.getInt(0);
}
break;
case Types.BIGINT :
if (o instanceof java.lang.Long) {
return o;
}
if (o instanceof java.lang.String) {
o = Library.trim((String) o, " ", true, true);
long val = Long.parseLong((String) o);
return ValuePool.getLong(val);
}
if (o instanceof java.lang.Integer) {
return ValuePool.getLong(((Integer) o).longValue());
}
if (o instanceof Byte || o instanceof Short) {
return ValuePool.getLong(Number.intValue(o));
}
if (Number.isNumber(o)/*o instanceof java.lang.Number*/) {
return convertToLong(o);
}
if (o instanceof java.lang.Boolean) {
return ((Boolean) o).booleanValue()
? ValuePool.getLong(1)
: ValuePool.getLong(0);
}
break;
case Types.REAL :
case Types.FLOAT :
case Types.DOUBLE :
if (o instanceof java.lang.Double) {
return o;
}
if (o instanceof java.lang.String) {
o = Library.trim((String) o, " ", true, true);
double d = JavaSystem.parseDouble((String) o);
long l = Double.doubleToLongBits(d);
return ValuePool.getDouble(l);
}
if (Number.isNumber(o)/*o instanceof java.lang.Number*/) {
return convertToDouble(o);
}
if (o instanceof java.lang.Boolean) {
return ((Boolean) o).booleanValue()
? ValuePool.getDouble(1)
: ValuePool.getDouble(0);
}
break;
case Types.NUMERIC :
case Types.DECIMAL :
if (o instanceof BigDecimal) {
return o;
}
if (o instanceof Long) {
return BigDecimal.valueOf(((Long) o).longValue());
}
if (o instanceof java.lang.Boolean) {
return ((Boolean) o).booleanValue() ? BIG_DECIMAL_1
: BIG_DECIMAL_0;
}
break;
case Types.BOOLEAN :
if (o instanceof java.lang.Boolean) {
return (Boolean) o;
}
if (o instanceof java.lang.String) {
o = Library.trim((String) o, " ", true, true);
return ((String) o).equalsIgnoreCase("TRUE")
? Boolean.TRUE
: Boolean.FALSE;
}
if (o instanceof Integer) {
return ((Integer) o).intValue() == 0 ? Boolean.FALSE
: Boolean.TRUE;
}
if (o instanceof Long) {
return ((Long) o).longValue() == 0L ? Boolean.FALSE
: Boolean.TRUE;
}
if (o instanceof java.lang.Double) {
return ((Double) o).doubleValue() == 0.0
? Boolean.FALSE
: Boolean.TRUE;
}
if (o instanceof BigDecimal) {
return ((BigDecimal) o).equals(BIG_DECIMAL_0)
? Boolean.FALSE
: Boolean.TRUE;
}
throw Trace.error(Trace.WRONG_DATA_TYPE);
case Types.VARCHAR_IGNORECASE :
case Types.VARCHAR :
case Types.CHAR :
case Types.LONGVARCHAR :
if (o instanceof java.lang.String) {
return o;
}
if (o instanceof Time) {
return HsqlDateTime.getTimeString((Time) o, null);
}
if (o instanceof Timestamp) {
return HsqlDateTime.getTimestampString((Timestamp) o,
null);
}
if (o instanceof Date) {
return HsqlDateTime.getDateString((Date) o, null);
}
if (o instanceof byte[]) {
return StringConverter.byteToHex((byte[]) o);
}
break;
case Types.TIME :
if (o instanceof Time) {
return HsqlDateTime.getNormalisedTime((Time) o);
}
if (o instanceof Timestamp) {
return HsqlDateTime.getNormalisedTime((Timestamp) o);
}
if (o instanceof String) {
return HsqlDateTime.timeValue((String) o);
}
if (o instanceof Date) {
throw Trace.error(Trace.INVALID_CONVERSION,
Types.getTypeString(type));
}
break;
case Types.TIMESTAMP :
if (o instanceof Timestamp) {
return o;
}
if (o instanceof Time) {
return HsqlDateTime.getNormalisedTimestamp((Time) o);
}
if (o instanceof Date) {
return HsqlDateTime.getNormalisedTimestamp((Date) o);
}
if (o instanceof String) {
return HsqlDateTime.timestampValue((String) o);
}
break;
case Types.DATE :
if (o instanceof Date) {
return HsqlDateTime.getNormalisedDate((Date) o);
}
if (o instanceof Timestamp) {
return HsqlDateTime.getNormalisedDate((Timestamp) o);
}
if (o instanceof String) {
return HsqlDateTime.dateValue((String) o);
}
if (o instanceof Time) {
throw Trace.error(Trace.INVALID_CONVERSION,
Types.getTypeString(type));
}
break;
case Types.BINARY :
case Types.VARBINARY :
case Types.LONGVARBINARY :
if (o instanceof Binary) {
return o;
} else if (o instanceof byte[]) {
return new Binary((byte[]) o, false);
} else if (o instanceof String) {
/**
* @todo fredt - we need this for script processing only
* handle the script separately and process normal
* conversion according to rules in SQL
* standard
*/
return new Binary(
StringConverter.hexToByte((String) o), false);
}
throw Trace.error(Trace.INVALID_CONVERSION,
Types.getTypeString(type));
// fredt@users 20030708 - patch 1.7.2 - OBJECT handling - superseded
case Types.OTHER :
throw new RuntimeException("Unknown type");
/*if (o instanceof JavaObject) {
return o;
} else if (o instanceof String) {*/
/**
* @todo fredt - we need this for script processing only
* handle the script separately and allow normal Sting
* objects to be stored as JavaObject
*/
/* return new JavaObject(
StringConverter.hexToByte((String) o));
} else if (o instanceof Binary) {
return new JavaObject(((Binary) o).getBytes());
}
return new JavaObject((Serializable) o);*/
default :
}
if (o instanceof JavaObject) {
o = ((JavaObject) o).getObject();
return convertObject(o, type);
}
return convertString(o.toString(), type);
} catch (HsqlException e) {
throw e;
} catch (Exception e) {
throw Trace.error(Trace.WRONG_DATA_TYPE, e.toString());
}
}
/**
* Return a java object based on a SQL string. This is called from
* convertObject(Object o, int type).
*
* @param s
* @param type
* @return
* @throws HsqlException
*/
private static Object convertString(String s,
int type) throws HsqlException {
switch (type) {
case Types.TINYINT :
case Types.SMALLINT :
// fredt - do maximumm / minimum checks on each type
return convertObject(s, type);
case Types.INTEGER :
int val = Integer.parseInt(s);
return ValuePool.getInt(val);
case Types.BIGINT :
return ValuePool.getLong(Long.parseLong(s));
case Types.REAL :
case Types.FLOAT :
case Types.DOUBLE :
double d = JavaSystem.parseDouble(s);
long l = Double.doubleToLongBits(d);
return ValuePool.getDouble(l);
case Types.VARCHAR_IGNORECASE :
case Types.VARCHAR :
case Types.CHAR :
case Types.LONGVARCHAR :
return s;
case Types.DATE :
return HsqlDateTime.dateValue(s);
case Types.TIME :
return HsqlDateTime.timeValue(s);
case Types.TIMESTAMP :
return HsqlDateTime.timestampValue(s);
case Types.NUMERIC :
case Types.DECIMAL :
s = Library.trim(s, " ", true, true);
return new BigDecimal(s);
case Types.BOOLEAN :
return s.equalsIgnoreCase("TRUE") ? Boolean.TRUE
: Boolean.FALSE;
case Types.BINARY :
case Types.VARBINARY :
case Types.LONGVARBINARY :
case Types.OTHER :
default :
throw Trace.error(Trace.INVALID_CONVERSION,
Types.getTypeString(type));
}
}
/**
* Return an SQL representation of an object. Strings will be quoted
* with single quotes, other objects will represented as in a SQL
* statement.
*
* @param o
* @param type
* @return result
* @throws HsqlException
*/
static String createSQLString(Object o, int type) throws HsqlException {
if (o == null) {
return "NULL";
}
switch (type) {
case Types.NULL :
return "NULL";
case Types.REAL :
case Types.FLOAT :
case Types.DOUBLE :
return createSQLString(Number.doubleValue(o));
case Types.DATE :
case Types.TIME :
case Types.TIMESTAMP :
return StringConverter.toQuotedString(o.toString(), '\'',
false);
case Types.BINARY :
case Types.VARBINARY :
case Types.LONGVARBINARY :
if (!(o instanceof Binary)) {
throw Trace.error(Trace.INVALID_CONVERSION);
}
return StringConverter.toQuotedString(
StringConverter.byteToHex(((Binary) o).getBytes()), '\'',
false);
case Types.OTHER :
if (!(o instanceof JavaObject)) {
throw Trace.error(Trace.SERIALIZATION_FAILURE);
}
return StringConverter.toQuotedString(
StringConverter.byteToHex(((JavaObject) o).getBytes()),
'\'', false);
case Types.VARCHAR_IGNORECASE :
case Types.VARCHAR :
case Types.CHAR :
case Types.LONGVARCHAR :
return createSQLString((String) o);
default :
return o.toString();
}
}
public static String createSQLString(double x) {
if (x == Double.NEGATIVE_INFINITY) {
return "-1E0/0";
}
if (x == Double.POSITIVE_INFINITY) {
return "1E0/0";
}
if (Double.isNaN(x)) {
return "0E0/0E0";
}
String s = Double.toString(x);
// ensure the engine treats the value as a DOUBLE, not DECIMAL
if (s.indexOf('E') < 0) {
s = s.concat("E0");
}
return s;
}
/**
* Turns a java string into a quoted SQL string
*
* @param s java string
* @return quoted SQL string
*/
public static String createSQLString(String s) {
if (s == null) {
return "NULL";
}
return StringConverter.toQuotedString(s, '\'', true);
}
/**
* Explicit casts are handled here. This is separate from the implicit
* casts carried out when inserting/updating rows.
* SQL standard 6.12 rules for enforcement of size, precision and scale
* are implemented here are as follows:
*
* For no size, precision or scale, default to convertObject(Object)
*
* Right truncation is allowed only for CHAR to CHAR casts
* (CHAR is generic for all string types).
*
* For other casts to CHAR, right truncation is not allowed.
*
* For numeric conversions, scale is always converted to target first,
* then precision is imposed. No truncation is allowed. (fredt)
*/
public static Object convertObject(Session session, Object o, int type,
int precision,
int scale) throws HsqlException {
if (o == null) {
return o;
}
if (precision == 0) {
return convertObject(o, type);
}
boolean check = true;
switch (type) {
case Types.VARCHAR_IGNORECASE :
case Types.LONGVARCHAR :
type = Types.VARCHAR;
case Types.VARCHAR :
case Types.CHAR :
if (o instanceof String) {
check = false;
} else {
o = convertObject(o, Types.VARCHAR);
}
return enforceSize(o, type, precision, scale, check);
case Types.NUMERIC :
case Types.DECIMAL :
if (!(o instanceof BigDecimal)) {
o = convertObject(o, type);
}
return enforceSize(o, type, precision, scale, true);
case Types.TIMESTAMP :
if (o instanceof Time) {
long millis = session.currentDate.getTime()
+ ((Time) o).getTime();
o = HsqlDateTime.getTimestamp(millis);
}
if (o instanceof Timestamp) {
return enforceSize(o, type, precision, scale, false);
}
}
return convertObject(o, type);
}
static int[] tenPower = {
1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000
};
/**
* Check an object for type CHAR and VARCHAR and truncate/pad based on
* the size
*
* @param obj object to check
* @param type the object type
* @param size size to enforce
* @param check throw if too long
* @return the altered object if the right type, else the object
* passed in unaltered
* @throws HsqlException if data too long
*/
static Object enforceSize(Object obj, int type, int size, int scale,
boolean check) throws HsqlException {
if (obj == null) {
return obj;
}
if (size == 0 && type != Types.TIMESTAMP) {
return obj;
}
// todo: need to handle BINARY like this as well
switch (type) {
case Types.CHAR :
return checkChar((String) obj, size, check);
case Types.VARCHAR :
case Types.VARCHAR_IGNORECASE :
return checkVarchar((String) obj, size, check);
case Types.NUMERIC :
case Types.DECIMAL :
BigDecimal dec = (BigDecimal) obj;
dec = dec.setScale(scale, BigDecimal.ROUND_HALF_DOWN);
BigInteger big = JavaSystem.getUnscaledValue(dec);
int sign = big.signum() == -1 ? 1
: 0;
if (big.toString().length() - sign > size) {
throw Trace.error(Trace.STRING_DATA_TRUNCATION);
}
return dec;
case Types.TIMESTAMP :
if (size == 6) {
return obj;
}
Timestamp ts = (Timestamp) obj;
int nanos = ts.getNanos();
int divisor = tenPower[size];
int newNanos = (nanos / divisor) * divisor;
ts.setNanos(newNanos);
return ts;
default :
return obj;
}
}
/**
* Checks the length of a VARCHAR string.
*
* @param s the string to pad to truncate
* @param len the len to make the string
* @param check if true, throw an exception if truncation takes place
* @return the string of size len
*/
static String checkVarchar(String s, int len,
boolean check) throws HsqlException {
int slen = s.length();
if (slen > len) {
if (check) {
throw Trace.error(Trace.STRING_DATA_TRUNCATION);
}
return s.substring(0, len);
}
return s;
}
/**
* Checks and pads a CHARACTER string to len size
*
* @param s the string to pad to truncate
* @param len the len to make the string
* @param check if true, throw an exception if truncation takes place
* @return the string of size len
*/
static String checkChar(String s, int len,
boolean check) throws HsqlException {
int slen = s.length();
if (slen == len) {
return s;
}
if (slen > len) {
if (check) {
throw Trace.error(Trace.STRING_DATA_TRUNCATION);
}
return s.substring(0, len);
}
char[] b = new char[len];
s.getChars(0, slen, b, 0);
for (int i = slen; i < len; i++) {
b[i] = ' ';
}
return new String(b);
}
/**
* Type narrowing from DOUBLE/DECIMAL/NUMERIC to BIGINT / INT / SMALLINT / TINYINT
* following the SQL rules. When conversion is from a non-integral type,
* digits to the right of the decimal point are lost.
*/
/**
* Converter from a numeric object to Integer. Input is checked to be
* within range represented by Integer.
*/
static Integer convertToInt(Object o) throws HsqlException {
if (o instanceof BigDecimal) {
BigInteger bi = ((BigDecimal) o).toBigInteger();
if (bi.compareTo(MAX_INT) > 0 || bi.compareTo(MIN_INT) < 0) {
throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
}
return ValuePool.getInt(bi.intValue());
}
if (o instanceof Double || o instanceof Float) {
double d = Number.doubleValue(o);
if (Double.isNaN(d) || d >= (double) Integer.MAX_VALUE + 1
|| d <= (double) Integer.MIN_VALUE - 1) {
throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
}
return ValuePool.getInt((int) d);
}
throw Trace.error(Trace.INVALID_CONVERSION);
}
/**
* Converter from a numeric object to Long. Input is checked to be
* within range represented by Long.
*/
static Long convertToLong(Object o) throws HsqlException {
if (o instanceof BigDecimal) {
BigInteger bi = ((BigDecimal) o).toBigInteger();
if (bi.compareTo(MAX_LONG) > 0 || bi.compareTo(MIN_LONG) < 0) {
throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
}
return ValuePool.getLong(bi.longValue());
}
if (o instanceof Double || o instanceof Float) {
double d = Number.doubleValue(o);
if (Double.isNaN(d) || d >= (double) Long.MAX_VALUE + 1
|| d <= (double) Long.MIN_VALUE - 1) {
throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
}
return ValuePool.getLong((long) d);
}
throw Trace.error(Trace.INVALID_CONVERSION);
}
/**
* Converter from a numeric object to Double. Input is checked to be
* within range represented by Double
*/
static Double convertToDouble(Object o) throws HsqlException {
double val;
if (o instanceof BigDecimal) {
BigDecimal bd = (BigDecimal) o;
val = bd.doubleValue();
int signum = bd.signum();
BigDecimal bo = new BigDecimal(val + signum);
if (bo.compareTo(bd) != signum) {
throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
}
} else {
val = Number.doubleValue(o);
}
return ValuePool.getDouble(Double.doubleToLongBits(val));
}
// fredt@users 20020408 - patch 442993 by fredt - arithmetic expressions
/**
* Arithmetic expression terms are promoted to a type that can
* represent the resulting values and avoid incorrect results.
* When the result or the expression is converted to the * type of the target column for storage, an exception is thrown if the * resulting value cannot be stored in the column
* Returns a SQL type "wide" enough to represent the result of the
* expression.
* A type is "wider" than the other if it can represent all its
* numeric values.
* Types narrower than INTEGER (int) are promoted to
* INTEGER. The order is as follows
* * INTEGER, BIGINT, DOUBLE, DECIMAL
*
* TINYINT and SMALLINT in any combination return INTEGER
* INTEGER and INTEGER return BIGINT
* BIGINT and INTEGER return NUMERIC/DECIMAL
* BIGINT and BIGINT return NUMERIC/DECIMAL
* DOUBLE and INTEGER return DOUBLE
* DOUBLE and BIGINT return DOUBLE
* NUMERIC/DECIMAL and any type returns NUMERIC/DECIMAL
*
* @author fredt@users
* @param type1 java.sql.Types value for the first numeric type
* @param type2 java.sql.Types value for the second numeric type
* @return either type1 or type2 on the basis of the above order
*/
static int getCombinedNumberType(int type1, int type2, int expType) {
int typeWidth1 = getNumTypeWidth(type1);
int typeWidth2 = getNumTypeWidth(type2);
if (typeWidth1 == 16 || typeWidth2 == 16) {
return Types.DOUBLE;
}
switch (expType) {
case Expression.EQUAL :
case Expression.BIGGER :
case Expression.BIGGER_EQUAL :
case Expression.SMALLER_EQUAL :
case Expression.SMALLER :
case Expression.NOT_EQUAL :
case Expression.ALTERNATIVE :
case Expression.DIVIDE :
return (typeWidth1 > typeWidth2) ? type1
: type2;
default :
int sum = typeWidth1 + typeWidth2;
if (sum <= 4) {
return Types.INTEGER;
}
if (sum <= 8) {
return Types.BIGINT;
}
return Types.NUMERIC;
}
}
/**
* @param type java.sql.Types int for a numeric type
* @return relative width
*/
static int getNumTypeWidth(int type) {
switch (type) {
case Types.TINYINT :
return 1;
case Types.SMALLINT :
return 2;
case Types.INTEGER :
return 4;
case Types.BIGINT :
return 8;
case Types.REAL :
case Types.FLOAT :
case Types.DOUBLE :
return 16;
case Types.NUMERIC :
case Types.DECIMAL :
return 32;
default :
return 32;
}
}
/**
* returns -1, 0 , +1
*/
static int compareToTypeRange(Object o, int targettype) {
if (! Number.isNumber(o) ) {
return 0;
}
if (o instanceof Integer || o instanceof Long) {
long temp = Number.longValue(o);
int min;
int max;
switch (targettype) {
case Types.TINYINT :
min = Byte.MIN_VALUE;
max = Byte.MAX_VALUE;
break;
case Types.SMALLINT :
min = Short.MIN_VALUE;
max = Short.MAX_VALUE;
break;
case Types.INTEGER :
min = Integer.MIN_VALUE;
max = Integer.MAX_VALUE;
break;
case Types.BIGINT :
case Types.DECIMAL :
case Types.NUMERIC :
default :
return 0;
}
if (max < temp) {
return 1;
}
if (temp < min) {
return -1;
}
return 0;
} else {
try {
o = convertToLong(o);
return compareToTypeRange(o, targettype);
} catch (HsqlException e) {
if (e.getErrorCode() == -Trace.NUMERIC_VALUE_OUT_OF_RANGE) {
if (o instanceof BigDecimal) {
return ((BigDecimal) o).signum();
} else if (o instanceof Double) {
return ((Double) o).doubleValue() > 0 ? 1
: -1;
}
}
}
}
return 0;
}
/**
* Converts the specified hexadecimal digit String
* to an equivalent array of bytes.
*
* @param hexString a String
of hexadecimal digits
* @throws HsqlException if the specified string contains non-hexadecimal digits.
* @return a byte array equivalent to the specified string of hexadecimal digits
*/
public static byte[] hexToByteArray(String hexString)
throws HsqlException {
try {
return StringConverter.hexToByte(hexString);
} catch (IOException e) {
throw Trace.error(Trace.INVALID_CHARACTER_ENCODING);
}
}
/**
* Compares a byte[]
with another specified
* byte[]
for order. Returns a negative integer, zero,
* or a positive integer as the first object is less than, equal to, or
* greater than the specified second byte[]
.
* * @param o1 the first byte[] to be compared * @param o2 the second byte[] to be compared * @return a negative integer, zero, or a positive integer as this object * is less than, equal to, or greater than the specified object. */ static int compareTo(byte[] o1, byte[] o2) { int len = o1.length; int lenb = o2.length; for (int i = 0; ; i++) { int a = 0; int b = 0; if (i < len) { a = ((int) o1[i]) & 0xff; } else if (i >= lenb) { return 0; } if (i < lenb) { b = ((int) o2[i]) & 0xff; } if (a > b) { return 1; } if (b > a) { return -1; } } } }