All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.sqlserver.jdbc.DDC Maven / Gradle / Ivy

There is a newer version: 12.8.1.jre11
Show newest version
//---------------------------------------------------------------------------------------------------------------------------------
// File: DDC.java
//
//
// Microsoft JDBC Driver for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), 
//  to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//  and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
//  IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
 
 
package com.microsoft.sqlserver.jdbc;
import java.math.*;
import java.io.*;
import java.nio.charset.*;
import java.nio.*;
import java.text.*;
import java.util.*;

/**
 * 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();
		}
	}
	
	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();
		}
	}
	
	//this is how fx framework 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, RoundingMode.DOWN);

		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 
	 */
	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,
			String 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 "




© 2015 - 2024 Weber Informatics LLC | Privacy Policy