com.microsoft.sqlserver.jdbc.DDC Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mssql-jdbc Show documentation
Show all versions of mssql-jdbc Show documentation
Microsoft JDBC Driver for SQL Server.
/*
* Microsoft JDBC Driver for SQL Server
*
* Copyright(c) Microsoft Corporation All rights reserved.
*
* This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
*/
package com.microsoft.sqlserver.jdbc;
import static java.nio.charset.StandardCharsets.US_ASCII;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
/**
* Utility class for all Data Dependant Conversions (DDC).
*/
final class DDC {
/**
* Convert an Integer object to desired target user type.
*
* @param intvalue
* the value to convert.
* @param valueLength
* the value to convert.
* @param jdbcType
* the jdbc type required.
* @param streamType
* the type of stream required.
* @return the required object.
*/
static final Object convertIntegerToObject(int intValue,
int valueLength,
JDBCType jdbcType,
StreamType streamType) {
switch (jdbcType) {
case INTEGER:
return new Integer(intValue);
case SMALLINT: // 2.21 small and tinyint returned as short
case TINYINT:
return new Short((short) intValue);
case BIT:
case BOOLEAN:
return new Boolean(0 != intValue);
case BIGINT:
return new Long(intValue);
case DECIMAL:
case NUMERIC:
case MONEY:
case SMALLMONEY:
return new BigDecimal(Integer.toString(intValue));
case FLOAT:
case DOUBLE:
return new Double(intValue);
case REAL:
return new Float(intValue);
case BINARY:
return convertIntToBytes(intValue, valueLength);
default:
return Integer.toString(intValue);
}
}
/**
* Convert a Long object to desired target user type.
*
* @param longVal
* the value to convert.
* @param jdbcType
* the jdbc type required.
* @param baseSSType
* the base SQLServer type.
* @param streamType
* the stream type.
* @return the required object.
*/
static final Object convertLongToObject(long longVal,
JDBCType jdbcType,
SSType baseSSType,
StreamType streamType) {
switch (jdbcType) {
case BIGINT:
return new Long(longVal);
case INTEGER:
return new Integer((int) longVal);
case SMALLINT: // small and tinyint returned as short
case TINYINT:
return new Short((short) longVal);
case BIT:
case BOOLEAN:
return new Boolean(0 != longVal);
case DECIMAL:
case NUMERIC:
case MONEY:
case SMALLMONEY:
return new BigDecimal(Long.toString(longVal));
case FLOAT:
case DOUBLE:
return new Double(longVal);
case REAL:
return new Float(longVal);
case BINARY:
byte[] convertedBytes = convertLongToBytes(longVal);
int bytesToReturnLength = 0;
byte[] bytesToReturn;
switch (baseSSType) {
case BIT:
case TINYINT:
bytesToReturnLength = 1;
bytesToReturn = new byte[bytesToReturnLength];
System.arraycopy(convertedBytes, convertedBytes.length - bytesToReturnLength, bytesToReturn, 0, bytesToReturnLength);
return bytesToReturn;
case SMALLINT:
bytesToReturnLength = 2;
bytesToReturn = new byte[bytesToReturnLength];
System.arraycopy(convertedBytes, convertedBytes.length - bytesToReturnLength, bytesToReturn, 0, bytesToReturnLength);
return bytesToReturn;
case INTEGER:
bytesToReturnLength = 4;
bytesToReturn = new byte[bytesToReturnLength];
System.arraycopy(convertedBytes, convertedBytes.length - bytesToReturnLength, bytesToReturn, 0, bytesToReturnLength);
return bytesToReturn;
case BIGINT:
bytesToReturnLength = 8;
bytesToReturn = new byte[bytesToReturnLength];
System.arraycopy(convertedBytes, convertedBytes.length - bytesToReturnLength, bytesToReturn, 0, bytesToReturnLength);
return bytesToReturn;
default:
return convertedBytes;
}
case VARBINARY:
switch (baseSSType) {
case BIGINT:
return new Long(longVal);
case INTEGER:
return new Integer((int) longVal);
case SMALLINT: // small and tinyint returned as short
case TINYINT:
return new Short((short) longVal);
case BIT:
return new Boolean(0 != longVal);
case DECIMAL:
case NUMERIC:
case MONEY:
case SMALLMONEY:
return new BigDecimal(Long.toString(longVal));
case FLOAT:
return new Double(longVal);
case REAL:
return new Float(longVal);
case BINARY:
return convertLongToBytes(longVal);
default:
return Long.toString(longVal);
}
default:
return Long.toString(longVal);
}
}
/**
* Encodes an integer value to a byte array in big-endian order.
*
* @param intValue
* the integer value to encode.
* @param valueLength
* the number of bytes to encode.
* @return the byte array containing the big-endian encoded value.
*/
static final byte[] convertIntToBytes(int intValue,
int valueLength) {
byte bytes[] = new byte[valueLength];
for (int i = valueLength; i-- > 0;) {
bytes[i] = (byte) (intValue & 0xFF);
intValue >>= 8;
}
return bytes;
}
/**
* Convert a Float object to desired target user type.
*
* @param floatVal
* the value to convert.
* @param jdbcType
* the jdbc type required.
* @param streamType
* the stream type.
* @return the required object.
*/
static final Object convertFloatToObject(float floatVal,
JDBCType jdbcType,
StreamType streamType) {
switch (jdbcType) {
case REAL:
return new Float(floatVal);
case INTEGER:
return new Integer((int) floatVal);
case SMALLINT: // small and tinyint returned as short
case TINYINT:
return new Short((short) floatVal);
case BIT:
case BOOLEAN:
return new Boolean(0 != Float.compare(0.0f, floatVal));
case BIGINT:
return new Long((long) floatVal);
case DECIMAL:
case NUMERIC:
case MONEY:
case SMALLMONEY:
return new BigDecimal(Float.toString(floatVal));
case FLOAT:
case DOUBLE:
return new Double((new Float(floatVal)).doubleValue());
case BINARY:
return convertIntToBytes(Float.floatToRawIntBits(floatVal), 4);
default:
return Float.toString(floatVal);
}
}
/**
* Encodes a long value to a byte array in big-endian order.
*
* @param longValue
* the long value to encode.
* @return the byte array containing the big-endian encoded value.
*/
static final byte[] convertLongToBytes(long longValue) {
byte bytes[] = new byte[8];
for (int i = 8; i-- > 0;) {
bytes[i] = (byte) (longValue & 0xFF);
longValue >>= 8;
}
return bytes;
}
/**
* Convert a Double object to desired target user type.
*
* @param doubleVal
* the value to convert.
* @param jdbcType
* the jdbc type required.
* @param streamType
* the stream type.
* @return the required object.
*/
static final Object convertDoubleToObject(double doubleVal,
JDBCType jdbcType,
StreamType streamType) {
switch (jdbcType) {
case FLOAT:
case DOUBLE:
return new Double(doubleVal);
case REAL:
return new Float((new Double(doubleVal)).floatValue());
case INTEGER:
return new Integer((int) doubleVal);
case SMALLINT: // small and tinyint returned as short
case TINYINT:
return new Short((short) doubleVal);
case BIT:
case BOOLEAN:
return new Boolean(0 != Double.compare(0.0d, doubleVal));
case BIGINT:
return new Long((long) doubleVal);
case DECIMAL:
case NUMERIC:
case MONEY:
case SMALLMONEY:
return new BigDecimal(Double.toString(doubleVal));
case BINARY:
return convertLongToBytes(Double.doubleToRawLongBits(doubleVal));
default:
return Double.toString(doubleVal);
}
}
static final byte[] convertBigDecimalToBytes(BigDecimal bigDecimalVal,
int scale) {
byte[] valueBytes;
if (bigDecimalVal == null) {
valueBytes = new byte[2];
valueBytes[0] = (byte) scale;
valueBytes[1] = 0; // data length
}
else {
boolean isNegative = (bigDecimalVal.signum() < 0);
// NOTE: Handle negative scale as a special case for JDK 1.5 and later VMs.
if (bigDecimalVal.scale() < 0)
bigDecimalVal = bigDecimalVal.setScale(0);
BigInteger bi = bigDecimalVal.unscaledValue();
if (isNegative)
bi = bi.negate();
byte[] unscaledBytes = bi.toByteArray();
valueBytes = new byte[unscaledBytes.length + 3];
int j = 0;
valueBytes[j++] = (byte) bigDecimalVal.scale();
valueBytes[j++] = (byte) (unscaledBytes.length + 1); // data length + sign
valueBytes[j++] = (byte) (isNegative ? 0 : 1); // 1 = +ve, 0 = -ve
for (int i = unscaledBytes.length - 1; i >= 0; i--)
valueBytes[j++] = unscaledBytes[i];
}
return valueBytes;
}
/**
* Convert a BigDecimal object to desired target user type.
*
* @param bigDecimalVal
* the value to convert.
* @param jdbcType
* the jdbc type required.
* @param streamType
* the stream type.
* @return the required object.
*/
static final Object convertBigDecimalToObject(BigDecimal bigDecimalVal,
JDBCType jdbcType,
StreamType streamType) {
switch (jdbcType) {
case DECIMAL:
case NUMERIC:
case MONEY:
case SMALLMONEY:
return bigDecimalVal;
case FLOAT:
case DOUBLE:
return new Double(bigDecimalVal.doubleValue());
case REAL:
return new Float(bigDecimalVal.floatValue());
case INTEGER:
return new Integer(bigDecimalVal.intValue());
case SMALLINT: // small and tinyint returned as short
case TINYINT:
return new Short(bigDecimalVal.shortValue());
case BIT:
case BOOLEAN:
return new Boolean(0 != bigDecimalVal.compareTo(BigDecimal.valueOf(0)));
case BIGINT:
return new Long(bigDecimalVal.longValue());
case BINARY:
return convertBigDecimalToBytes(bigDecimalVal, bigDecimalVal.scale());
default:
return bigDecimalVal.toString();
}
}
/**
* Convert a Money object to desired target user type.
*
* @param bigDecimalVal
* the value to convert.
* @param jdbcType
* the jdbc type required.
* @param streamType
* the stream type.
* @param numberOfBytes
* the number of bytes to convert
* @return the required object.
*/
static final Object convertMoneyToObject(BigDecimal bigDecimalVal,
JDBCType jdbcType,
StreamType streamType,
int numberOfBytes) {
switch (jdbcType) {
case DECIMAL:
case NUMERIC:
case MONEY:
case SMALLMONEY:
return bigDecimalVal;
case FLOAT:
case DOUBLE:
return new Double(bigDecimalVal.doubleValue());
case REAL:
return new Float(bigDecimalVal.floatValue());
case INTEGER:
return new Integer(bigDecimalVal.intValue());
case SMALLINT: // small and tinyint returned as short
case TINYINT:
return new Short(bigDecimalVal.shortValue());
case BIT:
case BOOLEAN:
return new Boolean(0 != bigDecimalVal.compareTo(BigDecimal.valueOf(0)));
case BIGINT:
return new Long(bigDecimalVal.longValue());
case BINARY:
return convertToBytes(bigDecimalVal, bigDecimalVal.scale(), numberOfBytes);
default:
return bigDecimalVal.toString();
}
}
// converts big decimal to money and smallmoney
private static byte[] convertToBytes(BigDecimal value,
int scale,
int numBytes) {
boolean isNeg = value.signum() < 0;
value = value.setScale(scale);
BigInteger bigInt = value.unscaledValue();
byte[] unscaledBytes = bigInt.toByteArray();
byte[] ret = new byte[numBytes];
if (unscaledBytes.length < numBytes) {
for (int i = 0; i < numBytes - unscaledBytes.length; ++i) {
ret[i] = (byte) (isNeg ? -1 : 0);
}
}
int offset = numBytes - unscaledBytes.length;
for (int i = offset; i < numBytes; ++i) {
ret[i] = unscaledBytes[i - offset];
}
return ret;
}
/**
* Convert a byte array to desired target user type.
*
* @param bytesValue
* the value to convert.
* @param jdbcType
* the jdbc type required.
* @param baseTypeInfo
* the type information associated with bytesValue.
* @return the required object.
* @throws SQLServerException
* when an error occurs.
*/
static final Object convertBytesToObject(byte[] bytesValue,
JDBCType jdbcType,
TypeInfo baseTypeInfo) throws SQLServerException {
switch (jdbcType) {
case CHAR:
String str = Util.bytesToHexString(bytesValue, bytesValue.length);
if ((SSType.BINARY == baseTypeInfo.getSSType()) && (str.length() < (baseTypeInfo.getPrecision() * 2))) {
StringBuffer strbuf = new StringBuffer(str);
while (strbuf.length() < (baseTypeInfo.getPrecision() * 2)) {
strbuf.append('0');
}
return strbuf.toString();
}
return str;
case BINARY:
case VARBINARY:
case LONGVARBINARY:
if ((SSType.BINARY == baseTypeInfo.getSSType()) && (bytesValue.length < baseTypeInfo.getPrecision())) {
byte[] newBytes = new byte[baseTypeInfo.getPrecision()];
System.arraycopy(bytesValue, 0, newBytes, 0, bytesValue.length);
return newBytes;
}
return bytesValue;
default:
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedConversionFromTo"));
throw new SQLServerException(form.format(new Object[] {baseTypeInfo.getSSType().name(), jdbcType}), null, 0, null);
}
}
/**
* Convert a String object to desired target user type.
*
* @param stringVal
* the value to convert.
* @param charset
* the character set.
* @param jdbcType
* the jdbc type required.
* @return the required object.
*/
static final Object convertStringToObject(String stringVal,
Charset charset,
JDBCType jdbcType,
StreamType streamType) throws UnsupportedEncodingException, IllegalArgumentException {
switch (jdbcType) {
// Convert String to Numeric types.
case DECIMAL:
case NUMERIC:
case MONEY:
case SMALLMONEY:
return new BigDecimal(stringVal.trim());
case FLOAT:
case DOUBLE:
return Double.valueOf(stringVal.trim());
case REAL:
return Float.valueOf(stringVal.trim());
case INTEGER:
return Integer.valueOf(stringVal.trim());
case SMALLINT: // small and tinyint returned as short
case TINYINT:
return Short.valueOf(stringVal.trim());
case BIT:
case BOOLEAN:
String trimmedString = stringVal.trim();
return (1 == trimmedString.length()) ? Boolean.valueOf('1' == trimmedString.charAt(0)) : Boolean.valueOf(trimmedString);
case BIGINT:
return Long.valueOf(stringVal.trim());
// Convert String to Temporal types.
case TIMESTAMP:
return java.sql.Timestamp.valueOf(stringVal.trim());
case DATE:
return java.sql.Date.valueOf(getDatePart(stringVal.trim()));
case TIME: {
// Accepted character formats for conversion to java.sql.Time are:
// hh:mm:ss[.nnnnnnnnn]
// YYYY-MM-DD hh:mm:ss[.nnnnnnnnn]
//
// To handle either of these formats:
// 1) Normalize and parse as a Timestamp
// 2) Round fractional seconds up to the nearest millisecond (max resolution of java.sql.Time)
// 3) Renormalize (as rounding may have changed the date) to a java.sql.Time
java.sql.Timestamp ts = java.sql.Timestamp.valueOf(TDS.BASE_DATE_1970 + " " + getTimePart(stringVal.trim()));
GregorianCalendar cal = new GregorianCalendar(Locale.US);
cal.clear();
cal.setTimeInMillis(ts.getTime());
if (ts.getNanos() % Nanos.PER_MILLISECOND >= Nanos.PER_MILLISECOND / 2)
cal.add(Calendar.MILLISECOND, 1);
cal.set(TDS.BASE_YEAR_1970, Calendar.JANUARY, 1);
return new java.sql.Time(cal.getTimeInMillis());
}
case BINARY:
return stringVal.getBytes(charset);
default:
// For everything else, just return either a string or appropriate stream.
switch (streamType) {
case CHARACTER:
return new StringReader(stringVal);
case ASCII:
return new ByteArrayInputStream(stringVal.getBytes(US_ASCII));
case BINARY:
return new ByteArrayInputStream(stringVal.getBytes());
default:
return stringVal;
}
}
}
static final Object convertStreamToObject(BaseInputStream stream,
TypeInfo typeInfo,
JDBCType jdbcType,
InputStreamGetterArgs getterArgs) throws SQLServerException {
// Need to handle the simple case of a null value here, as it is not done
// outside this function.
if (null == stream)
return null;
assert null != typeInfo;
assert null != getterArgs;
SSType ssType = typeInfo.getSSType();
try {
switch (jdbcType) {
case CHAR:
case VARCHAR:
case LONGVARCHAR:
case NCHAR:
case NVARCHAR:
case LONGNVARCHAR:
default:
// Binary streams to character types:
// - Direct conversion to ASCII stream
// - Convert as hexized value to other character types
if (SSType.BINARY == ssType || SSType.VARBINARY == ssType || SSType.VARBINARYMAX == ssType || SSType.TIMESTAMP == ssType
|| SSType.IMAGE == ssType || SSType.UDT == ssType) {
if (StreamType.ASCII == getterArgs.streamType) {
return stream;
}
else {
assert StreamType.CHARACTER == getterArgs.streamType || StreamType.NONE == getterArgs.streamType;
byte[] byteValue = stream.getBytes();
if (JDBCType.GUID == jdbcType) {
return Util.readGUID(byteValue);
}
else {
String hexString = Util.bytesToHexString(byteValue, byteValue.length);
if (StreamType.NONE == getterArgs.streamType)
return hexString;
return new StringReader(hexString);
}
}
}
// Handle streams converting to ASCII
if (StreamType.ASCII == getterArgs.streamType) {
// Fast path for SBCS data that converts directly/easily to ASCII
if (typeInfo.supportsFastAsciiConversion())
return new AsciiFilteredInputStream(stream);
// Slightly less fast path for MBCS data that converts directly/easily to ASCII
if (getterArgs.isAdaptive) {
return AsciiFilteredUnicodeInputStream.MakeAsciiFilteredUnicodeInputStream(stream,
new BufferedReader(new InputStreamReader(stream, typeInfo.getCharset())));
}
else {
return new ByteArrayInputStream((new String(stream.getBytes(), typeInfo.getCharset())).getBytes(US_ASCII));
}
}
else if (StreamType.CHARACTER == getterArgs.streamType || StreamType.NCHARACTER == getterArgs.streamType) {
if (getterArgs.isAdaptive)
return new BufferedReader(new InputStreamReader(stream, typeInfo.getCharset()));
else
return new StringReader(new String(stream.getBytes(), typeInfo.getCharset()));
}
// None of the special/fast textual conversion cases applied. Just go the normal route of converting via String.
return convertStringToObject(new String(stream.getBytes(), typeInfo.getCharset()), typeInfo.getCharset(), jdbcType,
getterArgs.streamType);
case CLOB:
return new SQLServerClob(stream, typeInfo);
case NCLOB:
return new SQLServerNClob(stream, typeInfo);
case SQLXML:
return new SQLServerSQLXML(stream, getterArgs, typeInfo);
case BINARY:
case VARBINARY:
case LONGVARBINARY:
case BLOB:
// Where allowed, streams convert directly to binary representation
if (StreamType.BINARY == getterArgs.streamType)
return stream;
if (JDBCType.BLOB == jdbcType)
return new SQLServerBlob(stream);
return stream.getBytes();
}
}
// Conversion can throw either of these exceptions:
//
// UnsupportedEncodingException (binary conversions)
// IllegalArgumentException (any conversion - note: numerics throw NumberFormatException subclass)
//
// Catch them and translate them to a SQLException so that we don't propagate an unexpected exception
// type all the way up to the app, which may not catch it either...
catch (IllegalArgumentException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue"));
throw new SQLServerException(form.format(new Object[] {typeInfo.getSSType(), jdbcType}), null, 0, e);
}
catch (UnsupportedEncodingException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue"));
throw new SQLServerException(form.format(new Object[] {typeInfo.getSSType(), jdbcType}), null, 0, e);
}
}
// Returns date portion of string.
// Expects one of "" or "