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
/*
 * 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;
                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))) {

                    StringBuilder strbuf = new StringBuilder(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 "




© 2015 - 2024 Weber Informatics LLC | Privacy Policy