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: 62
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 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();
                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 if (JDBCType.GEOMETRY == jdbcType) {
                                if (!typeInfo.getSSTypeName().equalsIgnoreCase(jdbcType.toString())) {
                                    DataTypes.throwConversionError(typeInfo.getSSTypeName().toUpperCase(),
                                            jdbcType.toString());
                                }
                                return Geometry.STGeomFromWKB(byteValue);
                            } else if (JDBCType.GEOGRAPHY == jdbcType) {
                                if (!typeInfo.getSSTypeName().equalsIgnoreCase(jdbcType.toString())) {
                                    DataTypes.throwConversionError(typeInfo.getSSTypeName().toUpperCase(),
                                            jdbcType.toString());
                                }
                                return Geography.STGeomFromWKB(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);

            }
        }

        // 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