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

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

Go to download

Microsoft JDBC Driver for SQL Server. The Azure Key Vault feature in Microsoft JDBC Driver for SQL Server depends on Azure SDK for JAVA and Azure Active Directory Library For Java.

There is a newer version: 12.7.0.jre11-preview
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 intValue;
            case SMALLINT: // 2.21 small and tinyint returned as short
            case TINYINT:
                return (short) intValue;
            case BIT:
            case BOOLEAN:
                return 0 != intValue;
            case BIGINT:
                return (long) intValue;
            case DECIMAL:
            case NUMERIC:
            case MONEY:
            case SMALLMONEY:
                return new BigDecimal(Integer.toString(intValue));
            case FLOAT:
            case DOUBLE:
                return (double) intValue;
            case REAL:
                return (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 longVal;
            case INTEGER:
                return (int) longVal;
            case SMALLINT: // small and tinyint returned as short
            case TINYINT:
                return (short) longVal;
            case BIT:
            case BOOLEAN:
                return 0 != longVal;
            case DECIMAL:
            case NUMERIC:
            case MONEY:
            case SMALLMONEY:
                return new BigDecimal(Long.toString(longVal));
            case FLOAT:
            case DOUBLE:
                return (double) longVal;
            case REAL:
                return (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 longVal;
                    case INTEGER:
                        return (int) longVal;
                    case SMALLINT: // small and tinyint returned as short
                    case TINYINT:
                        return (short) longVal;
                    case BIT:
                        return 0 != longVal;
                    case DECIMAL:
                    case NUMERIC:
                    case MONEY:
                    case SMALLMONEY:
                        return new BigDecimal(Long.toString(longVal));
                    case FLOAT:
                        return (double) longVal;
                    case REAL:
                        return (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 floatVal;
            case INTEGER:
                return (int) floatVal;
            case SMALLINT: // small and tinyint returned as short
            case TINYINT:
                return (short) floatVal;
            case BIT:
            case BOOLEAN:
                return 0 != Float.compare(0.0f, floatVal);
            case BIGINT:
                return (long) floatVal;
            case DECIMAL:
            case NUMERIC:
            case MONEY:
            case SMALLMONEY:
                return new BigDecimal(Float.toString(floatVal));
            case FLOAT:
            case DOUBLE:
                return (Float.valueOf(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 doubleVal;
            case REAL:
                return (Double.valueOf(doubleVal)).floatValue();
            case INTEGER:
                return (int) doubleVal;
            case SMALLINT: // small and tinyint returned as short
            case TINYINT:
                return (short) doubleVal;
            case BIT:
            case BOOLEAN:
                return 0 != Double.compare(0.0d, doubleVal);
            case BIGINT:
                return (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 bigDecimalVal.doubleValue();
            case REAL:
                return bigDecimalVal.floatValue();
            case INTEGER:
                return bigDecimalVal.intValue();
            case SMALLINT: // small and tinyint returned as short
            case TINYINT:
                return bigDecimalVal.shortValue();
            case BIT:
            case BOOLEAN:
                return 0 != bigDecimalVal.compareTo(BigDecimal.valueOf(0));
            case BIGINT:
                return 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 bigDecimalVal.doubleValue();
            case REAL:
                return bigDecimalVal.floatValue();
            case INTEGER:
                return bigDecimalVal.intValue();
            case SMALLINT: // small and tinyint returned as short
            case TINYINT:
                return bigDecimalVal.shortValue();
            case BIT:
            case BOOLEAN:
                return 0 != bigDecimalVal.compareTo(BigDecimal.valueOf(0));
            case BIGINT:
                return 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;
        System.arraycopy(unscaledBytes, 0, ret, offset, numBytes - 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