// jTDS JDBC Driver for Microsoft SQL Server and Sybase
// Copyright (C) 2004 The jTDS Project
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
package net.sourceforge.jtds.jdbc;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import net.sourceforge.jtds.util.Logger;
/**
* This class contains static utility methods designed to support the
* main driver classes.
* <p>
* Implementation notes:
* <ol>
* <li>The methods in this class incorporate some code from previous versions
* of jTDS to handle dates, BLobs etc.
* <li>This class contains routines to generate runtime messages from the resource file.
* <li>The key data conversion logic used in Statements and result sets is implemented here.
* <li>There is nothing here which is TDS specific.
* </ol>
*
* @author Mike Hutchinson
* @author jTDS project
* @version $Id: Support.java,v 1.56.2.6 2010-05-17 09:36:57 ickzon Exp $
*/
public class Support {
// Constants used in datatype conversions to avoid object allocations.
private static final Integer INTEGER_ZERO = new Integer(0);
private static final Integer INTEGER_ONE = new Integer(1);
private static final Long LONG_ZERO = new Long(0L);
private static final Long LONG_ONE = new Long(1L);
private static final Float FLOAT_ZERO = new Float(0.0);
private static final Float FLOAT_ONE = new Float(1.0);
private static final Double DOUBLE_ZERO = new Double(0.0);
private static final Double DOUBLE_ONE = new Double(1.0);
private static final BigDecimal BIG_DECIMAL_ZERO = new BigDecimal(0.0);
private static final BigDecimal BIG_DECIMAL_ONE = new BigDecimal(1.0);
private static final java.sql.Date DATE_ZERO = new java.sql.Date(0);
private static final java.sql.Time TIME_ZERO = new java.sql.Time(0);
private static final BigInteger MIN_VALUE_LONG_BI = new BigInteger(String.valueOf(Long.MIN_VALUE));
private static final BigInteger MAX_VALUE_LONG_BI = new BigInteger(String.valueOf(Long.MAX_VALUE));
private static final BigDecimal MIN_VALUE_LONG_BD = new BigDecimal(String.valueOf(Long.MIN_VALUE));
private static final BigDecimal MAX_VALUE_LONG_BD = new BigDecimal(String.valueOf(Long.MAX_VALUE));
private static final BigInteger MAX_VALUE_28 = new BigInteger("9999999999999999999999999999");
private static final BigInteger MAX_VALUE_38 = new BigInteger("99999999999999999999999999999999999999");
/**
* Convert java clases to java.sql.Type constant.
*/
private static final HashMap typeMap = new HashMap();
static {
typeMap.put(Byte.class, new Integer(java.sql.Types.TINYINT));
typeMap.put(Short.class, new Integer(java.sql.Types.SMALLINT));
typeMap.put(Integer.class, new Integer(java.sql.Types.INTEGER));
typeMap.put(Long.class, new Integer(java.sql.Types.BIGINT));
typeMap.put(Float.class, new Integer(java.sql.Types.REAL));
typeMap.put(Double.class, new Integer(java.sql.Types.DOUBLE));
typeMap.put(BigDecimal.class, new Integer(java.sql.Types.DECIMAL));
typeMap.put(Boolean.class, new Integer(JtdsStatement.BOOLEAN));
typeMap.put(byte[].class, new Integer(java.sql.Types.VARBINARY));
typeMap.put(java.sql.Date.class, new Integer(java.sql.Types.DATE));
typeMap.put(java.sql.Time.class, new Integer(java.sql.Types.TIME));
typeMap.put(java.sql.Timestamp.class, new Integer(java.sql.Types.TIMESTAMP));
typeMap.put(BlobImpl.class, new Integer(java.sql.Types.LONGVARBINARY));
typeMap.put(ClobImpl.class, new Integer(java.sql.Types.LONGVARCHAR));
typeMap.put(String.class, new Integer(java.sql.Types.VARCHAR));
typeMap.put(Blob.class, new Integer(java.sql.Types.LONGVARBINARY));
typeMap.put(Clob.class, new Integer(java.sql.Types.LONGVARCHAR));
// bug #626
typeMap.put(BigInteger.class, new Integer(java.sql.Types.BIGINT));
}
/**
* Hex constants to use in conversion routines.
*/
private static final char hex[] = {'0', '1', '2', '3', '4', '5', '6','7',
'8', '9', 'A', 'B', 'C', 'D', 'E','F'
};
/**
* Convert a byte[] object to a hex string.
*
* @param bytes The byte array to convert.
* @return The hex equivalent as a <code>String</code>.
*/
public static String toHex(byte[] bytes) {
int len = bytes.length;
if (len > 0) {
StringBuilder buf = new StringBuilder(len * 2);
for (int i = 0; i < len; i++) {
int b1 = bytes[i] & 0xFF;
buf.append(hex[b1 >> 4]);
buf.append(hex[b1 & 0x0F]);
}
return buf.toString();
}
return "";
}
/**
* Normalize a BigDecimal value so that it fits within the
* available precision.
*
* @param value The decimal value to normalize.
* @param maxPrecision The decimal precision supported by the server
* (assumed to be a value of either 28 or 38).
* @return The possibly normalized decimal value as a <code>BigDecimal</code>.
* @throws SQLException If the number is too big.
*/
static BigDecimal normalizeBigDecimal(BigDecimal value, int maxPrecision)
throws SQLException {
if (value == null) {
return null;
}
if (value.scale() < 0) {
// Java 1.5 BigDecimal allows negative scales.
// jTDS cannot send these so re-scale.
value = value.setScale(0);
}
if (value.scale() > maxPrecision) {
// This is an optimization to quickly adjust the scale of a
// very precise BD value. For example
// BigDecimal((double)1.0/3.0) yields a BD 54 digits long!
value = value.setScale(maxPrecision, BigDecimal.ROUND_HALF_UP);
}
BigInteger max = (maxPrecision == TdsData.DEFAULT_PRECISION_28) ? MAX_VALUE_28 : MAX_VALUE_38;
while (value.abs().unscaledValue().compareTo(max) > 0) {
// OK we need to reduce the scale if possible to preserve
// the integer part of the number and still fit within the
// available precision.
int scale = value.scale() - 1;
if (scale < 0) {
// Can't do it number just too big
throw new SQLException(Messages.get("error.normalize.numtoobig",
String.valueOf(maxPrecision)), "22000");
}
value = value.setScale(scale, BigDecimal.ROUND_HALF_UP);
}
return value;
}
static Object castNumeric(Object orig, int sourceType, int targetType) {
return null;
}
/**
* Convert an existing data object to the specified JDBC type.
*
* @param callerReference an object reference to the caller of this method;
* must be a <code>Connection</code>,
* <code>Statement</code> or <code>ResultSet</code>
* @param x the data object to convert
* @param jdbcType the required type constant from
* <code>java.sql.Types</code>
* @return the converted data object
* @throws SQLException if the conversion is not supported or fails
*/
static Object convert(Object callerReference, Object x, int jdbcType, String charSet)
throws SQLException {
// handle null value
if (x == null) {
switch (jdbcType) {
case java.sql.Types.BIT:
case JtdsStatement.BOOLEAN:
return Boolean.FALSE;
case java.sql.Types.TINYINT:
case java.sql.Types.SMALLINT:
case java.sql.Types.INTEGER:
return INTEGER_ZERO;
case java.sql.Types.BIGINT:
return LONG_ZERO;
case java.sql.Types.REAL:
return FLOAT_ZERO;
case java.sql.Types.FLOAT:
case java.sql.Types.DOUBLE:
return DOUBLE_ZERO;
default:
return null;
}
}
try {
switch (jdbcType) {
case java.sql.Types.TINYINT:
if (x instanceof Boolean) {
return ((Boolean) x).booleanValue() ? INTEGER_ONE : INTEGER_ZERO;
} else if (x instanceof Byte) {
return new Integer(((Byte)x).byteValue() & 0xFF);
} else {
long val;
if (x instanceof Number) {
val = ((Number)x).longValue();
} else if (x instanceof String) {
val = new Long(((String) x).trim()).longValue();
} else {
break;
}
if (val < Byte.MIN_VALUE || val > Byte.MAX_VALUE) {
throw new SQLException(Messages.get("error.convert.numericoverflow", x, getJdbcTypeName(jdbcType)), "22003");
} else {
return new Integer(new Long(val).intValue());
}
}
case java.sql.Types.SMALLINT:
if (x instanceof Boolean) {
return ((Boolean) x).booleanValue() ? INTEGER_ONE : INTEGER_ZERO;
} else if (x instanceof Short) {
return new Integer(((Short)x).shortValue());
} else if (x instanceof Byte) {
return new Integer(((Byte)x).byteValue() & 0xFF);
} else {
long val;
if (x instanceof Number) {
val = ((Number)x).longValue();
} else if (x instanceof String) {
val = new Long(((String) x).trim()).longValue();
} else {
break;
}
if (val < Short.MIN_VALUE || val > Short.MAX_VALUE) {
throw new SQLException(Messages.get("error.convert.numericoverflow", x, getJdbcTypeName(jdbcType)), "22003");
} else {
return new Integer(new Long(val).intValue());
}
}
case java.sql.Types.INTEGER:
if (x instanceof Integer) {
return x;
}
else if (x instanceof Boolean) {
return ((Boolean) x).booleanValue() ? INTEGER_ONE : INTEGER_ZERO;
} else if (x instanceof Short) {
return new Integer(((Short)x).shortValue());
} else if (x instanceof Byte) {
return new Integer(((Byte)x).byteValue() & 0xFF);
} else {
long val;
if (x instanceof Number) {
val = ((Number)x).longValue();
} else if (x instanceof String) {
val = new Long(((String) x).trim()).longValue();
} else {
break;
}
if (val < Integer.MIN_VALUE || val > Integer.MAX_VALUE) {
throw new SQLException(Messages.get("error.convert.numericoverflow", x, getJdbcTypeName(jdbcType)), "22003");
} else {
return new Integer(new Long(val).intValue());
}
}
case java.sql.Types.BIGINT:
if (x instanceof BigDecimal ) {
BigDecimal val = (BigDecimal) x;
if (val.compareTo(MIN_VALUE_LONG_BD) < 0 || val.compareTo(MAX_VALUE_LONG_BD) > 0) {
throw new SQLException(Messages.get("error.convert.numericoverflow", x, getJdbcTypeName(jdbcType)), "22003");
} else {
return new Long(val.longValue());
}
} else if (x instanceof Long) {
return x;
} else if (x instanceof Boolean) {
return ((Boolean) x).booleanValue() ? LONG_ONE : LONG_ZERO;
} else if (x instanceof Byte) {
return new Long(((Byte)x).byteValue() & 0xFF);
} else if (x instanceof BigInteger) {
BigInteger val = (BigInteger) x;
if (val.compareTo(MIN_VALUE_LONG_BI) < 0 || val.compareTo(MAX_VALUE_LONG_BI) > 0) {
throw new SQLException(Messages.get("error.convert.numericoverflow", x, getJdbcTypeName(jdbcType)), "22003");
} else {
return new Long(val.longValue());
}
} else if (x instanceof Number) {
return new Long(((Number) x).longValue());
} else if (x instanceof String) {
return new Long(((String) x).trim());
} else {
break;
}
case java.sql.Types.REAL:
if (x instanceof Float) {
return x;
} else if (x instanceof Byte) {
return new Float(((Byte)x).byteValue() & 0xFF);
} else if (x instanceof Number) {
return new Float(((Number) x).floatValue());
} else if (x instanceof String) {
return new Float(((String) x).trim());
} else if (x instanceof Boolean) {
return ((Boolean) x).booleanValue() ? FLOAT_ONE : FLOAT_ZERO;
}
break;
case java.sql.Types.FLOAT:
case java.sql.Types.DOUBLE:
if (x instanceof Double) {
return x;
} else if (x instanceof Byte) {
return new Double(((Byte)x).byteValue() & 0xFF);
} else if (x instanceof Number) {
return new Double(((Number) x).doubleValue());
} else if (x instanceof String) {
return new Double(((String) x).trim());
} else if (x instanceof Boolean) {
return ((Boolean) x).booleanValue() ? DOUBLE_ONE : DOUBLE_ZERO;
}
break;
case java.sql.Types.NUMERIC:
case java.sql.Types.DECIMAL:
if (x instanceof BigDecimal) {
return x;
} else if (x instanceof Number) {
return new BigDecimal(x.toString());
} else if (x instanceof String) {
return new BigDecimal((String) x);
} else if (x instanceof Boolean) {
return ((Boolean) x).booleanValue() ? BIG_DECIMAL_ONE : BIG_DECIMAL_ZERO;
}
break;
case java.sql.Types.VARCHAR:
case java.sql.Types.CHAR:
if (x instanceof String) {
return x;
} else if (x instanceof Number) {
return x.toString();
} else if (x instanceof Boolean) {
return((Boolean) x).booleanValue() ? "1" : "0";
} else if (x instanceof Clob) {
Clob clob = (Clob) x;
long length = clob.length();
if (length > Integer.MAX_VALUE) {
throw new SQLException(Messages.get("error.normalize.lobtoobig"),
"22000");
}
return clob.getSubString(1, (int) length);
} else if (x instanceof Blob) {
Blob blob = (Blob) x;
long length = blob.length();
if (length > Integer.MAX_VALUE) {
throw new SQLException(Messages.get("error.normalize.lobtoobig"),
"22000");
}
x = blob.getBytes(1, (int) length);
}
if (x instanceof byte[]) {
return toHex((byte[])x);
}
return x.toString(); // Last hope!
case java.sql.Types.BIT:
case JtdsStatement.BOOLEAN:
if (x instanceof Boolean) {
return x;
} else if (x instanceof Number) {
return(((Number) x).intValue() == 0) ? Boolean.FALSE : Boolean.TRUE;
} else if (x instanceof String) {
String tmp = ((String) x).trim();
return ("1".equals(tmp) || "true".equalsIgnoreCase(tmp)) ? Boolean.TRUE : Boolean.FALSE;
}
break;
case java.sql.Types.VARBINARY:
case java.sql.Types.BINARY:
if (x instanceof byte[]) {
return x;
} else if (x instanceof Blob) {
Blob blob = (Blob) x;
return blob.getBytes(1, (int) blob.length());
} else if (x instanceof Clob) {
Clob clob = (Clob) x;
long length = clob.length();
if (length > Integer.MAX_VALUE) {
throw new SQLException(Messages.get("error.normalize.lobtoobig"),
"22000");
}
x = clob.getSubString(1, (int) length);
}
if (x instanceof String) {
//
// Strictly speaking this conversion is not required by
// the JDBC standard but jTDS has always supported it.
//
if (charSet == null) {
charSet = "ISO-8859-1";
}
try {
return ((String) x).getBytes(charSet);
} catch (UnsupportedEncodingException e) {
return ((String) x).getBytes();
}
} else if (x instanceof UniqueIdentifier) {
return ((UniqueIdentifier) x).getBytes();
}
break;
case java.sql.Types.TIMESTAMP:
if (x instanceof DateTime) {
return ((DateTime) x).toTimestamp();
} else if (x instanceof java.sql.Timestamp) {
return x;
} else if (x instanceof java.sql.Date) {
return new java.sql.Timestamp(((java.sql.Date) x).getTime());
} else if (x instanceof java.sql.Time) {
return new java.sql.Timestamp(((java.sql.Time) x).getTime());
} else if (x instanceof java.lang.String) {
String val = ( (String) x ).trim();
int len = val.length();
try
{
// TIMESTAMP (format: yyyy-[m]m-[d]d [h]h:[m]m:[s]s[.f...])
if( len > 10 && val.charAt( 4 ) == '-' )
{
return Timestamp.valueOf( val );
}
// maybe a DATE (format: yyyy-[m]m-[d]d)
else if( len > 7 && val.charAt( 4 ) == '-' )
{
return new Timestamp( Date.valueOf( val ).getTime() );
}
// maybe a TIME (format: [h]h:[m]m:[s]s)
else if( len > 7 && val.charAt( 2 ) == ':' )
{
// get rid of fractions of seconds
return new Timestamp( Time.valueOf( val.split("\\.")[0].trim() ).getTime() );
}
}
catch( IllegalArgumentException ie )
{
// format exception thrown below
}
throw new SQLException( Messages.get("error.convert.badnumber", val, getJdbcTypeName( jdbcType ) ), "22000" );
}
break;
case java.sql.Types.DATE:
if (x instanceof DateTime) {
return ((DateTime) x).toDate();
} else if (x instanceof java.sql.Date) {
return x;
} else if (x instanceof java.sql.Time) {
return DATE_ZERO;
} else if (x instanceof java.sql.Timestamp) {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime((java.util.Date) x);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
// VM1.4+ only return new java.sql.Date(cal.getTimeInMillis());
return new java.sql.Date(cal.getTime().getTime());
} else if (x instanceof java.lang.String) {
String val = ( (String) x ).trim();
int len = val.length();
try
{
// DATE (format: yyyy-[m]m-[d]d)
if( len > 7 && len < 11 && val.charAt( 4 ) == '-' )
{
return Date.valueOf( val );
}
// maybe a TIMESTAMP (format: yyyy-[m]m-[d]d [h]h:[m]m:[s]s[.f...])
else if( len > 10 && val.charAt( 4 ) == '-' )
{
// get rid of the time part
return Date.valueOf( val.split( " " )[0].trim() );
}
// maybe a TIME (format: [h]h:[m]m:[s]s)
// optional conversion not required by the JDBC specs
else if( len > 7 && val.charAt( 2 ) == ':' )
{
// get rid of fractions of seconds
Time.valueOf( val.split( "\\." )[0].trim() ); // ensure parsable date
return DATE_ZERO;
}
}
catch( IllegalArgumentException ie )
{
// format exception thrown below
}
throw new SQLException( Messages.get("error.convert.badnumber", val, getJdbcTypeName( jdbcType ) ), "22000" );
}
break;
case java.sql.Types.TIME:
if (x instanceof DateTime) {
return ((DateTime) x).toTime();
} else if (x instanceof java.sql.Time) {
return x;
} else if (x instanceof java.sql.Date) {
return TIME_ZERO;
} else if (x instanceof java.sql.Timestamp) {
GregorianCalendar cal = new GregorianCalendar();
// VM 1.4+ only cal.setTimeInMillis(((java.sql.Timestamp)x).getTime());
cal.setTime((java.util.Date)x);
cal.set(Calendar.YEAR, 1970);
cal.set(Calendar.MONTH, 0);
cal.set(Calendar.DAY_OF_MONTH,1);
// VM 1.4+ only return new java.sql.Time(cal.getTimeInMillis());*/
return new java.sql.Time(cal.getTime().getTime());
} else if (x instanceof java.lang.String) {
// get rid of fractions of seconds
String val = ( (String) x ).trim().split( "\\." )[0].trim();
int len = val.length();
try
{
// TIME (format: [h]h:[m]m:[s]s)
if( len == 8 && val.charAt( 2 ) == ':' )
{
return Time.valueOf( val );
}
// maybe a TIMESTAMP (format: yyyy-[m]m-[d]d [h]h:[m]m:[s]s[.f...])
else if( len > 10 && val.charAt( 4 ) == '-' )
{
// get rid of the date part
String[] lines = val.split( " " );
if( lines.length > 1 )
{
return Time.valueOf( lines[1].trim() );
}
}
// maybe a DATE (format: yyyy-[m]m-[d]d)
// optional conversion not required by the JDBC specs
else if( len > 7 && val.charAt( 4 ) == '-' )
{
Date.valueOf( val ); // ensure parsable date
return TIME_ZERO;
}
}
catch( IllegalArgumentException ie )
{
// format exception thrown below
}
throw new SQLException( Messages.get("error.convert.badnumber", val, getJdbcTypeName( jdbcType ) ), "22000" );
}
break;
case java.sql.Types.OTHER:
return x;
case java.sql.Types.JAVA_OBJECT:
throw new SQLException(
Messages.get("error.convert.badtypes",
x.getClass().getName(),
getJdbcTypeName(jdbcType)), "22005");
case java.sql.Types.LONGVARBINARY:
case java.sql.Types.BLOB:
if (x instanceof Blob) {
return x;
} else if (x instanceof byte[]) {
return new BlobImpl(getConnection(callerReference), (byte[]) x);
} else if (x instanceof Clob) {
//
// Convert CLOB to BLOB. Not required by the standard but we will
// do it anyway.
//
Clob clob = (Clob) x;
try {
if (charSet == null) {
charSet = "ISO-8859-1";
}
Reader rdr = clob.getCharacterStream();
BlobImpl blob = new BlobImpl(getConnection(callerReference));
BufferedWriter out =
new BufferedWriter(new OutputStreamWriter(blob.setBinaryStream(1), charSet));
// TODO Use a buffer to improve performance
int c;
while ((c = rdr.read()) >= 0) {
out.write(c);
}
out.close();
rdr.close();
return blob;
} catch (UnsupportedEncodingException e) {
// Unlikely to happen but fall back on in memory copy
x = clob.getSubString(1, (int) clob.length());
} catch (IOException e) {
throw new SQLException(Messages.get("error.generic.ioerror", e.getMessage()),
"HY000");
}
}
if (x instanceof String) {
//
// Strictly speaking this conversion is also not required by
// the JDBC standard but jTDS has always supported it.
//
BlobImpl blob = new BlobImpl(getConnection(callerReference));
String data = (String) x;
if (charSet == null) {
charSet = "ISO-8859-1";
}
try {
blob.setBytes(1, data.getBytes(charSet));
} catch (UnsupportedEncodingException e) {
blob.setBytes(1, data.getBytes());
}
return blob;
}
break;
case java.sql.Types.LONGVARCHAR:
case java.sql.Types.CLOB:
if (x instanceof Clob) {
return x;
} else if (x instanceof Blob) {
//
// Convert BLOB to CLOB
//
Blob blob = (Blob) x;
try {
InputStream is = blob.getBinaryStream();
ClobImpl clob = new ClobImpl(getConnection(callerReference));
Writer out = clob.setCharacterStream(1);
// TODO Use a buffer to improve performance
int b;
// These reads/writes are buffered by the underlying blob buffers
while ((b = is.read()) >= 0) {
out.write(hex[b >> 4]);
out.write(hex[b & 0x0F]);
}
out.close();
is.close();
return clob;
} catch (IOException e) {
throw new SQLException(Messages.get("error.generic.ioerror", e.getMessage()),
"HY000");
}
} else if (x instanceof Boolean) {
x = ((Boolean) x).booleanValue() ? "1" : "0";
} else if (!(x instanceof byte[])) {
x = x.toString();
}
if (x instanceof byte[]) {
ClobImpl clob = new ClobImpl(getConnection(callerReference));
clob.setString(1, toHex((byte[]) x));
return clob;
} else if (x instanceof String) {
return new ClobImpl(getConnection(callerReference), (String) x);
}
break;
default:
throw new SQLException(
Messages.get("error.convert.badtypeconst", String.valueOf(x),
getJdbcTypeName(jdbcType)), "HY004");
}
throw new SQLException(
Messages.get("error.convert.badtypes",
x.getClass().getName(),
getJdbcTypeName(jdbcType)), "22005");
} catch (NumberFormatException nfe) {
throw new SQLException(
Messages.get("error.convert.badnumber", String.valueOf(x),
getJdbcTypeName(jdbcType)), "22000");
}
}
/**
* Get the JDBC type constant which matches the supplied Object type.
*
* @param value The object to analyse.
* @return The JDBC type constant as an <code>int</code>.
*/
static int getJdbcType(Object value) {
if (value == null) {
return java.sql.Types.NULL;
}
return getJdbcType(value.getClass());
}
/**
* Get the JDBC type constant which matches the supplied <code>Class</code>.
*
* @param typeClass the <code>Class</code> to analyse
* @return the JDBC type constant as an <code>int</code>
*/
static int getJdbcType(Class typeClass) {
if (typeClass == null) {
return java.sql.Types.JAVA_OBJECT;
}
Object type = typeMap.get(typeClass);
if (type == null) {
// not in typeMap - try recursion through superclass hierarchy
return getJdbcType(typeClass.getSuperclass());
}
return ((Integer) type).intValue();
}
/**
* Get a String describing the supplied JDBC type constant.
*
* @param jdbcType The constant to be decoded.
* @return The text decode of the type constant as a <code>String</code>.
*/
static String getJdbcTypeName(int jdbcType) {
switch (jdbcType) {
case java.sql.Types.ARRAY: return "ARRAY";
case java.sql.Types.BIGINT: return "BIGINT";
case java.sql.Types.BINARY: return "BINARY";
case java.sql.Types.BIT: return "BIT";
case java.sql.Types.BLOB: return "BLOB";
case JtdsStatement.BOOLEAN: return "BOOLEAN";
case java.sql.Types.CHAR: return "CHAR";
case java.sql.Types.CLOB: return "CLOB";
case JtdsStatement.DATALINK: return "DATALINK";
case java.sql.Types.DATE: return "DATE";
case java.sql.Types.DECIMAL: return "DECIMAL";
case java.sql.Types.DISTINCT: return "DISTINCT";
case java.sql.Types.DOUBLE: return "DOUBLE";
case java.sql.Types.FLOAT: return "FLOAT";
case java.sql.Types.INTEGER: return "INTEGER";
case java.sql.Types.JAVA_OBJECT: return "JAVA_OBJECT";
case java.sql.Types.LONGVARBINARY: return "LONGVARBINARY";
case java.sql.Types.LONGVARCHAR: return "LONGVARCHAR";
case java.sql.Types.NULL: return "NULL";
case java.sql.Types.NUMERIC: return "NUMERIC";
case java.sql.Types.OTHER: return "OTHER";
case java.sql.Types.REAL: return "REAL";
case java.sql.Types.REF: return "REF";
case java.sql.Types.SMALLINT: return "SMALLINT";
case java.sql.Types.STRUCT: return "STRUCT";
case java.sql.Types.TIME: return "TIME";
case java.sql.Types.TIMESTAMP: return "TIMESTAMP";
case java.sql.Types.TINYINT: return "TINYINT";
case java.sql.Types.VARBINARY: return "VARBINARY";
case java.sql.Types.VARCHAR: return "VARCHAR";
case java.sql.Types.SQLXML: return "XML";
default: return "ERROR";
}
}
/**
* Retrieve the fully qualified java class name for the
* supplied JDBC Types constant.
*
* @param jdbcType The JDBC Types constant.
* @return The fully qualified java class name as a <code>String</code>.
*/
static String getClassName(int jdbcType) {
switch (jdbcType) {
case JtdsStatement.BOOLEAN:
case java.sql.Types.BIT:
return "java.lang.Boolean";
case java.sql.Types.TINYINT:
case java.sql.Types.SMALLINT:
case java.sql.Types.INTEGER:
return "java.lang.Integer";
case java.sql.Types.BIGINT:
return "java.lang.Long";
case java.sql.Types.NUMERIC:
case java.sql.Types.DECIMAL:
return "java.math.BigDecimal";
case java.sql.Types.REAL:
return "java.lang.Float";
case java.sql.Types.FLOAT:
case java.sql.Types.DOUBLE:
return "java.lang.Double";
case java.sql.Types.CHAR:
case java.sql.Types.VARCHAR:
return "java.lang.String";
case java.sql.Types.BINARY:
case java.sql.Types.VARBINARY:
return "[B";
case java.sql.Types.LONGVARBINARY:
case java.sql.Types.BLOB:
return "java.sql.Blob";
case java.sql.Types.LONGVARCHAR:
case java.sql.Types.CLOB:
return "java.sql.Clob";
case java.sql.Types.DATE:
return "java.sql.Date";
case java.sql.Types.TIME:
return "java.sql.Time";
case java.sql.Types.TIMESTAMP:
return "java.sql.Timestamp";
}
return "java.lang.Object";
}
/**
* Embed the data object as a string literal in the buffer supplied.
*
* @param buf The buffer in which the data will be embedded.
* @param value The data object.
* @param isUnicode Set to <code>true</code> if Unicode strings should be used, else <code>false</code>.
* @param connection The {@link JtdsConnection} object.
*/
static void embedData(StringBuilder buf, Object value, boolean isUnicode, JtdsConnection connection)
throws SQLException {
buf.append(' ');
if (value == null) {
buf.append("NULL ");
return;
}
if (value instanceof Blob) {
Blob blob = (Blob) value;
value = blob.getBytes(1, (int) blob.length());
} else if (value instanceof Clob) {
Clob clob = (Clob) value;
value = clob.getSubString(1, (int) clob.length());
}
if (value instanceof DateTime) {
buf.append('\'');
buf.append(value);
buf.append('\'');
} else
if (value instanceof byte[]) {
byte[] bytes = (byte[]) value;
int len = bytes.length;
if (len >= 0) {
buf.append('0').append('x');
if (len == 0 && connection.getTdsVersion() < Driver.TDS70) {
// Zero length binary values are not allowed
buf.append('0').append('0');
} else {
for (int i = 0; i < len; i++) {
int b1 = bytes[i] & 0xFF;
buf.append(hex[b1 >> 4]);
buf.append(hex[b1 & 0x0F]);
}
}
}
} else if (value instanceof String) {
String tmp = (String) value;
int len = tmp.length();
if (isUnicode) {
buf.append('N');
}
buf.append('\'');
for (int i = 0; i < len; i++) {
char c = tmp.charAt(i);
if (c == '\'') {
buf.append('\'');
}
buf.append(c);
}
buf.append('\'');
} else if (value instanceof java.sql.Date) {
DateTime dt = new DateTime((java.sql.Date)value);
buf.append('\'');
buf.append(dt);
buf.append('\'');
} else if (value instanceof java.sql.Time) {
DateTime dt = new DateTime((java.sql.Time)value);
buf.append('\'');
buf.append(dt);
buf.append('\'');
} else if (value instanceof java.sql.Timestamp) {
DateTime dt = new DateTime((java.sql.Timestamp)value);
buf.append('\'');
buf.append(dt);
buf.append('\'');
} else if (value instanceof Boolean) {
buf.append(((Boolean) value).booleanValue() ? '1' : '0');
} else if (value instanceof BigDecimal) {
//
// Ensure large decimal number does not overflow the
// maximum precision of the server.
// Main problem is with small numbers e.g. BigDecimal(1.0).toString() =
// 0.1000000000000000055511151231....
//
String tmp = value.toString();
int maxlen = connection.getMaxPrecision();
if (tmp.charAt(0) == '-') {
maxlen++;
}
if (tmp.indexOf('.') >= 0) {
maxlen++;
}
if (tmp.length() > maxlen) {
buf.append(tmp.substring(0, maxlen));
} else {
buf.append(tmp);
}
} else {
buf.append(value.toString());
}
buf.append(' ');
}
/**
* Generates a unique statement key for a given SQL statement.
*
* @param sql the sql statement to generate the key for
* @param params the statement parameters
* @param serverType the type of server to generate the key for
* @param catalog the catalog is required for uniqueness on Microsoft
* SQL Server
* @param autoCommit true if in auto commit mode
* @param cursor true if this is a prepared cursor
* @return the unique statement key
*/
static String getStatementKey(String sql, ParamInfo[] params,
int serverType, String catalog,
boolean autoCommit, boolean cursor) {
StringBuilder key;
if (serverType == Driver.SQLSERVER) {
key = new StringBuilder(1 + catalog.length() + sql.length()
+ 11 * params.length);
// Need to distinguish otherwise identical SQL for cursor and
// non cursor prepared statements (sp_prepare/sp_cursorprepare).
key.append((cursor) ? 'C':'X');
// Need to ensure that the current database is included in the key
// as procedures and handles are database specific.
key.append(catalog);
// Now the actual SQL statement
key.append(sql);
//
// Append parameter data types to key.
//
for (int i = 0; i < params.length; i++) {
key.append(params[i].sqlType);
}
} else {
key = new StringBuilder(sql.length() + 2);
// A simple key works for Sybase just need to know if
// proc created in chained mode or not.
key.append((autoCommit) ? 'T': 'F');
// Now the actual SQL statement
key.append(sql);
}
return key.toString();
}
/**
* Constructs a parameter definition string for use with
* sp_executesql, sp_prepare, sp_prepexec, sp_cursoropen,
* sp_cursorprepare and sp_cursorprepexec.
*
* @param parameters Parameters to construct the definition for
* @return a parameter definition string
*/
static String getParameterDefinitions(ParamInfo[] parameters) {
StringBuilder sql = new StringBuilder(parameters.length * 15);
// Build parameter descriptor
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].name == null) {
sql.append("@P");
sql.append(i);
} else {
sql.append(parameters[i].name);
}
sql.append(' ');
sql.append(parameters[i].sqlType);
if (i + 1 < parameters.length) {
sql.append(',');
}
}
return sql.toString();
}
/**
* Update the SQL string and replace the ? markers with parameter names
* eg @P0, @P1 etc.
*
* @param sql the SQL containing markers to substitute
* @param list the parameter list
* @return the modified SQL as a <code>String</code>
*/
static String substituteParamMarkers(String sql, ParamInfo[] list) {
// A parameter can have at most 8 characters: " @P" plus at most 4
// digits plus " ". We subtract the "?" placeholder, that's at most
// 7 extra characters needed for each parameter.
char[] buf = new char[sql.length() + list.length * 7];
int bufferPtr = 0; // Output buffer pointer
int start = 0; // Input string pointer
StringBuilder number = new StringBuilder(4);
for (int i = 0; i < list.length; i++) {
int pos = list[i].markerPos;
if (pos > 0) {
sql.getChars(start, pos, buf, bufferPtr);
bufferPtr += (pos - start);
start = pos + 1;
// Append " @P"
buf[bufferPtr++] = ' ';
buf[bufferPtr++] = '@';
buf[bufferPtr++] = 'P';
// Append parameter number
// Rather complicated, but it's the only way in which no
// unnecessary objects are created
number.setLength(0);
number.append(i);
number.getChars(0, number.length(), buf, bufferPtr);
bufferPtr += number.length();
// Append " "
buf[bufferPtr++] = ' ';
}
}
if (start < sql.length()) {
sql.getChars(start, sql.length(), buf, bufferPtr);
bufferPtr += (sql.length() - start);
}
return new String(buf, 0, bufferPtr);
}
/**
* Substitute actual data for the parameter markers to simulate
* parameter substitution in a PreparedStatement.
*
* @param sql The SQL containing parameter markers to substitute.
* @param list The parameter descriptors.
* @param connection The current connection.
* @return The modified SQL statement.
*/
static String substituteParameters(String sql, ParamInfo[] list, JtdsConnection connection)
throws SQLException {
int len = sql.length();
for (int i = 0; i < list.length; i++) {
if (!list[i].isRetVal && !list[i].isSet && !list[i].isOutput) {
throw new SQLException(Messages.get("error.prepare.paramnotset",
Integer.toString(i+1)),
"07000");
}
Object value = list[i].value;
if (value instanceof java.io.InputStream
|| value instanceof java.io.Reader) {
try {
if (list[i].jdbcType == java.sql.Types.LONGVARCHAR ||
list[i].jdbcType == java.sql.Types.CLOB ||
list[i].jdbcType == java.sql.Types.VARCHAR) {
// TODO: Should improve the character set handling here
value = list[i].getString("US-ASCII");
} else {
value = list[i].getBytes("US-ASCII");
}
// Replace the stream/reader with the String/byte[]
list[i].value = value;
} catch (java.io.IOException e) {
throw new SQLException(Messages.get("error.generic.ioerror",
e.getMessage()),
"HY000");
}
}
if (value instanceof String) {
len += ((String) value).length() + 5;
} else if (value instanceof byte[]) {
len += ((byte[]) value).length * 2 + 4;
} else {
len += 32; // Default size
}
}
StringBuilder buf = new StringBuilder(len + 16);
int start = 0;
for (int i = 0; i < list.length; i++) {
int pos = list[i].markerPos;
if (pos > 0) {
buf.append(sql.substring(start, list[i].markerPos));
start = pos + 1;
final boolean isUnicode = connection.getTdsVersion() >= Driver.TDS70 && list[i].isUnicode;
Support.embedData(buf, list[i].value, isUnicode, connection);
}
}
if (start < sql.length()) {
buf.append(sql.substring(start));
}
return buf.toString();
}
/**
* Encode a string into a byte array using the specified character set.
*
* @param cs The Charset name.
* @param value The value to encode.
* @return The value of the String as a <code>byte[]</code>.
*/
static byte[] encodeString(String cs, String value) {
try {
return value.getBytes(cs);
} catch (UnsupportedEncodingException e) {
return value.getBytes();
}
}
/**
* Link the original cause to an <code>SQLWarning</code>.
* <p>
* This convenience method calls {@link #linkException(Exception, Throwable)}
* and casts the result for cleaner code elsewhere.
*
* @param sqle The <code>SQLWarning</code> to enhance.
* @param cause The <code>Throwable</code> to link.
* @return The original <code>SQLWarning</code> object.
*/
public static SQLWarning linkException(SQLWarning sqle, Throwable cause) {
return (SQLWarning) linkException((Exception) sqle, cause);
}
/**
* Link the original cause to an <code>SQLException</code>.
* <p>
* This convenience method calls {@link #linkException(Exception, Throwable)}
* and casts the result for cleaner code elsewhere.
*
* @param sqle The <code>SQLException</code> to enhance.
* @param cause The <code>Throwable</code> to link.
* @return The original <code>SQLException</code> object.
*/
public static SQLException linkException(SQLException sqle, Throwable cause) {
return (SQLException) linkException((Exception) sqle, cause);
}
/**
* Link the original cause to an <code>Exception</code>.
* <p>
* If running under JVM 1.4+ the <code>Throwable.initCause(Throwable)</code>
* method will be invoked to chain the exception, else the exception is
* logged via the {@link Logger} class.
* Modeled after the code written by Brian Heineman.
*
* @param exception The <code>Exception</code> to enhance.
* @param cause The <code>Throwable</code> to link.
* @return The original <code>Exception</code> object.
*/
public static Throwable linkException(Exception exception, Throwable cause) {
Class exceptionClass = exception.getClass();
Class[] parameterTypes = new Class[] {Throwable.class};
Object[] arguments = new Object[] {cause};
try {
Method initCauseMethod = exceptionClass.getMethod("initCause", parameterTypes);
initCauseMethod.invoke(exception, arguments);
} catch (NoSuchMethodException e) {
// Best we can do; this method does not exist in older JVMs.
if (Logger.isActive()) {
Logger.logException((Exception) cause);
}
} catch (Exception e) {
// Ignore all other exceptions. Do not prevent the main exception
// from being returned if reflection fails for any reason.
}
return exception;
}
/**
* Convert a timestamp to a different Timezone.
*
* @param value the timestamp value
* @param target the <code>Calendar</code> containing the TimeZone
* @return the new timestamp value as a <code>long</code>
*/
public static long timeToZone(java.util.Date value, Calendar target) {
java.util.Date tmp = target.getTime();
try {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(value);
target.set(Calendar.HOUR_OF_DAY, cal.get(Calendar.HOUR_OF_DAY));
target.set(Calendar.MINUTE, cal.get(Calendar.MINUTE));
target.set(Calendar.SECOND, cal.get(Calendar.SECOND));
target.set(Calendar.MILLISECOND, cal.get(Calendar.MILLISECOND));
target.set(Calendar.YEAR, cal.get(Calendar.YEAR));
target.set(Calendar.MONTH, cal.get(Calendar.MONTH));
target.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH));
return target.getTime().getTime();
}
finally {
target.setTime(tmp);
}
}
/**
* Convert a timestamp from a different Timezone.
* @param value the timestamp value.
* @param target the Calendar containing the TimeZone.
* @return The new timestamp value as a <code>long</code>.
*/
public static long timeFromZone(java.util.Date value , Calendar target) {
java.util.Date tmp = target.getTime();
try {
GregorianCalendar cal = new GregorianCalendar();
target.setTime(value);
cal.set(Calendar.HOUR_OF_DAY, target.get(Calendar.HOUR_OF_DAY));
cal.set(Calendar.MINUTE, target.get(Calendar.MINUTE));
cal.set(Calendar.SECOND, target.get(Calendar.SECOND));
cal.set(Calendar.MILLISECOND, target.get(Calendar.MILLISECOND));
cal.set(Calendar.YEAR, target.get(Calendar.YEAR));
cal.set(Calendar.MONTH, target.get(Calendar.MONTH));
cal.set(Calendar.DAY_OF_MONTH, target.get(Calendar.DAY_OF_MONTH));
return cal.getTime().getTime();
}
finally {
target.setTime(tmp);
}
}
/**
* Converts a LOB to the equivalent Java type, i.e. <code>Clob</code> to
* <code>String</code> and <code>Blob</code> to <code>byte[]</code>. If the
* value passed is not a LOB object, it is left unchanged and no exception
* is thrown; the idea is to transparently convert only LOBs.
*
* @param value an object that may be a LOB
* @return if the value was a LOB, the equivalent Java object, otherwise
* the original value
* @throws SQLException if an error occurs while reading the LOB contents
*/
public static Object convertLOB(Object value) throws SQLException {
if (value instanceof Clob) {
Clob c = (Clob) value;
return c.getSubString(1, (int) c.length());
}
if (value instanceof Blob) {
Blob b = (Blob) value;
return b.getBytes(1, (int) b.length());
}
return value;
}
/**
* Converts a LOB type constant to the equivalent Java type constant, i.e.
* <code>Types.CLOB</code> to <code>Types.LONGVARCHAR</code> and
* <code>Types.BLOB</code> to <code>Types.LONGVARBINARY</code>. If the
* type passed is not that of a LOB, it is left unchanged and no exception
* is thrown; the idea is to transparently convert only LOB types.
*
* @param type a {@link Types} constant defining a JDBC type, possibly a
* LOB
* @return if the type was that of a LOB, the equivalent Java object type,
* otherwise the original type
*/
public static int convertLOBType(int type) {
switch (type) {
case Types.BLOB:
return Types.LONGVARBINARY;
case Types.CLOB:
return Types.LONGVARCHAR;
default:
return type;
}
}
/**
* Checks the <code>os.name</code> system property to see if it starts
* with "windows".
*
* @return <code>true</code> if <code>os.name</code> starts with "windows",
* else <code>false</code>.
*/
public static boolean isWindowsOS() {
return System.getProperty("os.name").toLowerCase().startsWith("windows");
}
// ------------- Private methods ---------
/**
* Returns the connection for a given <code>ResultSet</code>,
* <code>Statement</code> or <code>Connection</code> object.
*
* @param callerReference an object reference to the caller of this method;
* must be a <code>Connection</code>, <code>Statement</code> or
* <code>ResultSet</code>
* @return a connection
*/
private static JtdsConnection getConnection(Object callerReference) {
if (callerReference == null) {
throw new IllegalArgumentException("callerReference cannot be null.");
}
Connection connection;
try {
if (callerReference instanceof Connection) {
connection = (Connection) callerReference;
} else if (callerReference instanceof Statement) {
connection = ((Statement) callerReference).getConnection();
} else if (callerReference instanceof ResultSet) {
connection = ((ResultSet) callerReference).getStatement().getConnection();
} else {
throw new IllegalArgumentException("callerReference is invalid.");
}
} catch (SQLException e) {
throw new IllegalStateException(e.getMessage());
}
return (JtdsConnection) connection;
}
private Support() {
// Prevent an instance of this class being created.
}
/**
* Calculate the buffer size to use when buffering the <code>InputStream</code>
* for named pipes.
* <p/>
* The buffer size is tied directly to the packet size because each request
* to the <code>SmbNamedPipe</code> will send a request for a particular
* size of packet. In other words, if you only request 1 byte, the
* <code>SmbNamedPipe</code> will send a request out and only ask for 1 byte
* back. Buffering the expected packet size ensures that all of the data
* will be returned in the buffer without wasting any space.
*
* @param tdsVersion the TDS version for the connection
* @param packetSize requested packet size for the connection
* @return minimum default packet size if <code>packetSize == 0</code>,
* else <code>packetSize</code>
*/
static int calculateNamedPipeBufferSize(final int tdsVersion, final int packetSize) {
if (packetSize == 0) {
if (tdsVersion >= Driver.TDS70) {
return TdsCore.DEFAULT_MIN_PKT_SIZE_TDS70;
}
return TdsCore.MIN_PKT_SIZE;
}
return packetSize;
}
}