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

com.microsoft.sqlserver.jdbc.SQLServerDataTable 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 java.math.BigDecimal;
import java.text.MessageFormat;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;

public final class SQLServerDataTable {

    int rowCount = 0;
    int columnCount = 0;
    Map columnMetadata = null;
    Map rows = null;

    private String tvpName = null;

    /**
     * The constant in the Java programming language, sometimes referred to as a type code, that identifies the type TVP.
     * 
     * @throws SQLServerException
     *             when an error occurs
     */
    // Name used in CREATE TYPE
    public SQLServerDataTable() throws SQLServerException {
        columnMetadata = new LinkedHashMap<>();
        rows = new HashMap<>();
    }

    /**
     * Clears this data table.
     */
    public synchronized void clear() {
        rowCount = 0;
        columnCount = 0;
        columnMetadata.clear();
        rows.clear();
    }

    /**
     * Retrieves an iterator on the rows of the data table.
     * 
     * @return an iterator on the rows of the data table.
     */
    public synchronized Iterator> getIterator() {
        if ((null != rows) && (null != rows.entrySet())) {
            return rows.entrySet().iterator();
        }
        return null;
    }

    /**
     * Adds meta data for the specified column
     * 
     * @param columnName
     *            the name of the column
     * @param sqlType
     *            the sql type of the column
     * @throws SQLServerException
     *             when an error occurs
     */
    public synchronized void addColumnMetadata(String columnName,
            int sqlType) throws SQLServerException {
        // column names must be unique
        Util.checkDuplicateColumnName(columnName, columnMetadata);
        columnMetadata.put(columnCount++, new SQLServerDataColumn(columnName, sqlType));
    }

    /**
     * Adds meta data for the specified column
     * 
     * @param column
     *            the name of the column
     * @throws SQLServerException
     *             when an error occurs
     */
    public synchronized void addColumnMetadata(SQLServerDataColumn column) throws SQLServerException {
        // column names must be unique
        Util.checkDuplicateColumnName(column.columnName, columnMetadata);
        columnMetadata.put(columnCount++, column);
    }

    /**
     * Adds one row of data to the data table.
     * 
     * @param values
     *            values to be added in one row of data to the data table.
     * @throws SQLServerException
     *             when an error occurs
     */
    public synchronized void addRow(Object... values) throws SQLServerException {
        try {
            int columnCount = columnMetadata.size();

            if ((null != values) && values.length > columnCount) {
                MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_moreDataInRowThanColumnInTVP"));
                Object[] msgArgs = {};
                throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
            }

            Iterator> columnsIterator = columnMetadata.entrySet().iterator();
            Object[] rowValues = new Object[columnCount];
            int currentColumn = 0;
            while (columnsIterator.hasNext()) {
                Object val = null;

                if ((null != values) && (currentColumn < values.length) && (null != values[currentColumn]))
                    val = (null == values[currentColumn]) ? null : values[currentColumn];
                currentColumn++;
                Map.Entry pair = columnsIterator.next();
                JDBCType jdbcType = JDBCType.of(pair.getValue().javaSqlType);
                internalAddrow(jdbcType, val, rowValues, pair);
            }
            rows.put(rowCount++, rowValues);
        }
        catch (NumberFormatException e) {
            throw new SQLServerException(SQLServerException.getErrString("R_TVPInvalidColumnValue"), e);
        }
        catch (ClassCastException e) {
            throw new SQLServerException(SQLServerException.getErrString("R_TVPInvalidColumnValue"), e);
        }

    }
    
    /**
     * Adding rows one row of data to data table.
     * @param jdbcType The jdbcType
     * @param val The data value
     * @param rowValues Row of data
     * @param pair pair to be added to data table
     * @throws SQLServerException
     */
    private void internalAddrow(JDBCType jdbcType,
            Object val,
            Object[] rowValues,
            Map.Entry pair) throws SQLServerException {

        SQLServerDataColumn currentColumnMetadata = pair.getValue();
        boolean isColumnMetadataUpdated = false;
        boolean bValueNull;
        int nValueLen;
        switch (jdbcType) {
            case BIGINT:
                rowValues[pair.getKey()] = (null == val) ? null : Long.parseLong(val.toString());
                break;

            case BIT:
                rowValues[pair.getKey()] = (null == val) ? null : Boolean.parseBoolean(val.toString());
                break;

            case INTEGER:
                rowValues[pair.getKey()] = (null == val) ? null : Integer.parseInt(val.toString());
                break;

            case SMALLINT:
            case TINYINT:
                rowValues[pair.getKey()] = (null == val) ? null : Short.parseShort(val.toString());
                break;

            case DECIMAL:
            case NUMERIC:
                BigDecimal bd = null;
                if (null != val) {
                    bd = new BigDecimal(val.toString());
                    // BigDecimal#precision returns number of digits in the unscaled value.
                    // Say, for value 0.01, it returns 1 but the precision should be 3 for SQLServer
                    int precision = Util.getValueLengthBaseOnJavaType(bd, JavaType.of(bd), null, null, jdbcType);
                    if (bd.scale() > currentColumnMetadata.scale) {
                        currentColumnMetadata.scale = bd.scale();
                        isColumnMetadataUpdated = true;
                    }
                    if (precision > currentColumnMetadata.precision) {
                        currentColumnMetadata.precision = precision;
                        isColumnMetadataUpdated = true;
                    }

                    // precision equal: the maximum number of digits in integer part + the maximum scale
                    int numberOfDigitsIntegerPart = precision - bd.scale();
                    if (numberOfDigitsIntegerPart > currentColumnMetadata.numberOfDigitsIntegerPart) {
                        currentColumnMetadata.numberOfDigitsIntegerPart = numberOfDigitsIntegerPart;
                        isColumnMetadataUpdated = true;
                    }

                    if (isColumnMetadataUpdated) {
                        currentColumnMetadata.precision = currentColumnMetadata.scale + currentColumnMetadata.numberOfDigitsIntegerPart;
                        columnMetadata.put(pair.getKey(), currentColumnMetadata);
                    }
                }
                rowValues[pair.getKey()] = bd;
                break;

            case DOUBLE:
                rowValues[pair.getKey()] = (null == val) ? null : Double.parseDouble(val.toString());
                break;

            case FLOAT:
            case REAL:
                rowValues[pair.getKey()] = (null == val) ? null : Float.parseFloat(val.toString());
                break;

            case TIMESTAMP_WITH_TIMEZONE:
            case TIME_WITH_TIMEZONE:
                DriverJDBCVersion.checkSupportsJDBC42();
            case DATE:
            case TIME:
            case TIMESTAMP:
            case DATETIMEOFFSET:
            case DATETIME:
            case SMALLDATETIME:
                // Sending temporal types as string. Error from database is thrown if parsing fails
                // no need to send precision for temporal types, string literal will never exceed DataTypes.SHORT_VARTYPE_MAX_BYTES

                if (null == val)
                    rowValues[pair.getKey()] = null;
                // java.sql.Date, java.sql.Time and java.sql.Timestamp are subclass of java.util.Date
                else if (val instanceof java.util.Date)
                    rowValues[pair.getKey()] = val.toString();
                else if (val instanceof microsoft.sql.DateTimeOffset)
                    rowValues[pair.getKey()] = val.toString();
                else if (val instanceof OffsetDateTime)
                    rowValues[pair.getKey()] = val.toString();
                else if (val instanceof OffsetTime)
                    rowValues[pair.getKey()] = val.toString();
                else
                    rowValues[pair.getKey()] = (null == val) ? null : (String) val;
                break;

            case BINARY:
            case VARBINARY:
            case LONGVARBINARY:
                bValueNull = (null == val);
                nValueLen = bValueNull ? 0 : ((byte[]) val).length;

                if (nValueLen > currentColumnMetadata.precision) {
                    currentColumnMetadata.precision = nValueLen;
                    columnMetadata.put(pair.getKey(), currentColumnMetadata);
                }
                rowValues[pair.getKey()] = (bValueNull) ? null : (byte[]) val;

                break;

            case CHAR:
                if (val instanceof UUID && (val != null))
                    val = val.toString();
            case VARCHAR:
            case NCHAR:
            case NVARCHAR:
            case LONGVARCHAR:
            case LONGNVARCHAR:
            case SQLXML:
                bValueNull = (null == val);
                nValueLen = bValueNull ? 0 : (2 * ((String) val).length());

                if (nValueLen > currentColumnMetadata.precision) {
                    currentColumnMetadata.precision = nValueLen;
                    columnMetadata.put(pair.getKey(), currentColumnMetadata);
                }
                rowValues[pair.getKey()] = (bValueNull) ? null : (String) val;
                break;
            case SQL_VARIANT:
                JDBCType internalJDBCType;
                if (null == val) { // TODO:Check this later
                    throw new SQLServerException(SQLServerException.getErrString("R_invalidValueForTVPWithSQLVariant"), null);
                }
                JavaType javaType = JavaType.of(val);
                internalJDBCType = javaType.getJDBCType(SSType.UNKNOWN, jdbcType);
                internalAddrow(internalJDBCType, val, rowValues, pair);
                break;
            default:
                MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedDataTypeTVP"));
                Object[] msgArgs = {jdbcType};
                throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
        }
    }
    
    public synchronized Map getColumnMetadata() {
        return columnMetadata;
    }

    public String getTvpName() {
        return tvpName;
    }

    /**
     * Retrieves the column meta data of this data table.
     * @param tvpName
     *            the name of TVP
     */
    public void setTvpName(String tvpName) {
        this.tvpName = tvpName;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy