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

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

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

import java.time.*;

/**
 * Parameter represents a JDBC parameter value that is supplied with a prepared or callable
 * statement or an updatable result set. Parameter is JDBC type specific and is capable of
 * representing any Java native type as well as a number of Java object types including
 * binary and character streams.
 */

final class Parameter
{
	// Value type info for OUT parameters (excluding return status)
	private TypeInfo typeInfo;

	// For unencrypted paramters cryptometa will be null. For encrypted parameters it will hold encryption metadata.
	CryptoMetadata cryptoMeta = null;
	TypeInfo getTypeInfo() { return typeInfo; }
	final CryptoMetadata getCryptoMetadata() {return cryptoMeta;}

	private boolean shouldHonorAEForParameter = false;
	private boolean userProvidesPrecision = false;
	private boolean userProvidesScale = false;

	// The parameter type definition
	private String typeDefinition = null;
	boolean renewDefinition = false;

	// updated if sendStringParametersAsUnicode=true for setNString, setNCharacterStream, and setNClob methods
	private JDBCType jdbcTypeSetByUser = null;

	// set length of value for variable length type (String)
	private int valueLength = 0;

	private boolean forceEncryption = false;


	Parameter(boolean honorAE)
	{
		shouldHonorAEForParameter = honorAE;
	}

	// Flag set to true if this is a registered OUTPUT parameter.
	boolean isOutput() { return null != registeredOutDTV; }

	// Since a parameter can have only one type definition for both sending its value to the server (IN)
	// and getting its value from the server (OUT), we use the JDBC type of the IN parameter value if there
	// is one; otherwise we use the registered OUT param JDBC type.
	JDBCType getJdbcType() throws SQLServerException
	{
		return (null != inputDTV) ? inputDTV.getJdbcType() : JDBCType.UNKNOWN;
	}

	/**
	 * Used when sendStringParametersAsUnicode=true to derive the appropriate National Character Set
	 * JDBC type corresponding to the specified JDBC type.
	 */
	private static JDBCType getSSPAUJDBCType(JDBCType jdbcType)
	{
		switch (jdbcType)
		{
		case CHAR:        return JDBCType.NCHAR;
		case VARCHAR:     return JDBCType.NVARCHAR;
		case LONGVARCHAR: return JDBCType.LONGNVARCHAR;
		case CLOB:        return JDBCType.NCLOB;
		default:          return jdbcType;
		}
	}

	// For parameters whose underlying type is not represented by a JDBC type
	// the transport type reflects how the value is sent to the
	// server (e.g. JDBCType.CHAR for GUID parameters).
	void registerForOutput(JDBCType jdbcType, SQLServerConnection con) throws SQLServerException
	{
		// DateTimeOffset is not supported with SQL Server versions earlier than Katmai
		if (JDBCType.DATETIMEOFFSET == jdbcType && !con.isKatmaiOrLater())
		{
			throw new SQLServerException(
					SQLServerException.getErrString("R_notSupported"),
					SQLState.DATA_EXCEPTION_NOT_SPECIFIC,
					DriverError.NOT_SET,
					null);
		}

		// sendStringParametersAsUnicode
		// If set to true, this connection property tells the driver to send textual parameters
		// to the server as Unicode rather than MBCS.  This is accomplished here by re-tagging
		// the value with the appropriate corresponding Unicode type.
		if (con.sendStringParametersAsUnicode())
		{

			if(shouldHonorAEForParameter)
			{
				setJdbcTypeSetByUser(jdbcType);
			}

			jdbcType = getSSPAUJDBCType(jdbcType);
		}

		registeredOutDTV = new DTV();
		registeredOutDTV.setJdbcType(jdbcType);

		if (null == setterDTV)
			inputDTV = registeredOutDTV;

		resetOutputValue();
	}

	int scale = 0;

	// Scale requested for a DECIMAL and NUMERIC OUT parameter.  If the OUT parameter
	// is also non-null IN parameter, the scale will be the larger of this value and
	// the value of the IN parameter's scale.
	private int outScale = 4;
	int getOutScale() { return outScale; }

	void setOutScale(int outScale) { 
		this.outScale = outScale; 
		userProvidesScale = true;
	}

	// The parameter name
	private String name;
	private String schemaName;

	/*
	 * The different DTVs representing the parameter's value:
	 *
	 * getterDTV -
	 * The OUT value, if set, of the parameter after execution.  This is the value
	 * retrieved by CallableStatement getter methods.
	 *
	 * registeredOutDTV -
	 * The "IN" value corresponding to a SQL NULL with a JDBC type that was passed to the
	 * CallableStatement.registerOutParameter method.  Since SQL Server does not
	 * directly support OUT-only parameters (just IN and IN/OUT), the driver sends
	 * a null IN value for an OUT parameter, unless the application set an input value
	 * (setterDTV) as well.
	 *
	 * setterDTV -
	 * The IN value, if set, of the parameter.  This is the value set by PreparedStatement
	 * and CallableStatement setter methods.
	 *
	 * inputDTV -
	 * If set, refers to either setterDTV or registeredOutDTV depending on whether
	 * the parameter is IN, IN/OUT, or OUT-only.  If cleared (i.e. set to null),
	 * it means that no value is set for the parameter and that execution of the
	 * PreparedStatement or CallableStatement should throw a "parameter not set"
	 * exception.
	 *
	 * Note that if the parameter value is a stream, the driver consumes its contents
	 * it at execution and clears inputDTV and setterDTV so that the application must
	 * reset the parameter prior to the next execution to avoid getting a "parameter not set"
	 * exception.
	 */
	private DTV getterDTV;
	private DTV registeredOutDTV = null;
	private DTV setterDTV = null;
	private DTV inputDTV = null;

	/**
	 * Clones this Parameter object for use in a batch.
	 *
	 * The clone method creates a shallow clone of the Parameter object.  That is,
	 * the cloned instance references all of the same internal objects and state
	 * as the original.
	 *
	 * Note: this method is purposely NOT the Object.clone() method, as that method
	 * has specific requirements and semantics that we don't need here.
	 */
	final Parameter cloneForBatch()
	{
		Parameter clonedParam = new Parameter(shouldHonorAEForParameter);
		clonedParam.typeInfo = typeInfo;
		clonedParam.typeDefinition = typeDefinition;
		clonedParam.outScale = outScale;
		clonedParam.name = name;
		clonedParam.getterDTV = getterDTV;
		clonedParam.registeredOutDTV = registeredOutDTV;
		clonedParam.setterDTV = setterDTV;
		clonedParam.inputDTV = inputDTV;
		clonedParam.cryptoMeta = cryptoMeta;
		clonedParam.jdbcTypeSetByUser = jdbcTypeSetByUser;
		clonedParam.valueLength = valueLength;
		clonedParam.userProvidesPrecision = userProvidesPrecision;
		clonedParam.userProvidesScale = userProvidesScale;
		return clonedParam;
	}

	/**
	 * Skip value.
	 */	
	final void skipValue(TDSReader tdsReader,  boolean isDiscard)  throws SQLServerException
	{ 
		if (null == getterDTV)
			getterDTV = new DTV();  

		deriveTypeInfo(tdsReader); 

		getterDTV.skipValue(typeInfo, tdsReader, isDiscard);
	}

	/**
	 * Skip value.
	 */	
	final void skipRetValStatus(TDSReader tdsReader)  throws SQLServerException
	{ 

		StreamRetValue srv = new StreamRetValue();
		srv.setFromTDS(tdsReader);
	}

	// Clear an INPUT parameter value
	void clearInputValue()
	{
		setterDTV = null;
		inputDTV = registeredOutDTV;
	}

	// reset output value for re -execution
	// if there was old value reset it to a new DTV
	void resetOutputValue()
	{
		getterDTV = null;
		typeInfo = null;
	}

	void deriveTypeInfo( TDSReader tdsReader) throws SQLServerException
	{
		if(null == typeInfo)
		{
			typeInfo = TypeInfo.getInstance(tdsReader, true);

			if(shouldHonorAEForParameter && typeInfo.isEncrypted())
			{
				//In this case, method getCryptoMetadata(tdsReader) retrieves baseTypeInfo without cryptoMetadata, 
				//so save cryptoMetadata first.
				CekTableEntry cekEntry = cryptoMeta.getCekTableEntry();
				cryptoMeta = (new StreamRetValue()).getCryptoMetadata(tdsReader);
				cryptoMeta.setCekTableEntry(cekEntry);
			}
		}
	}

	void setFromReturnStatus(int returnStatus, SQLServerConnection con) throws SQLServerException
	{
		if (null == getterDTV)
			getterDTV = new DTV();

		getterDTV.setValue(null, JDBCType.INTEGER, new Integer(returnStatus), JavaType.INTEGER, null, null, null, con, getForceEncryption());
	}

	void setValue(
			JDBCType jdbcType,
			Object value,
			JavaType javaType,
			StreamSetterArgs streamSetterArgs,
			Calendar calendar,
			Integer precision,
			Integer scale,
			SQLServerConnection con,
			boolean forceEncrypt, SQLServerStatementColumnEncryptionSetting stmtColumnEncriptionSetting, 
			int parameterIndex, 
			String userSQL,
			String tvpName) throws SQLServerException
	{

		if (shouldHonorAEForParameter)
		{
			userProvidesPrecision = false;
			userProvidesScale = false;
			
			if(null != precision){
				userProvidesPrecision = true;
			}

			if(null != scale){
				userProvidesScale = true;
			}

			//for encrypted tinyint, we need to convert short value to byte value, 
			//otherwise it would be sent as smallint
			//Also, for setters, we are able to send tinyint to smallint
			//However, for output parameter, it might cause error.
			if(!isOutput()){
				if((JavaType.SHORT == javaType) && 
					((JDBCType.TINYINT == jdbcType) || (JDBCType.SMALLINT == jdbcType))){
					// value falls in the TINYINT range
					if(((Short)value) >= 0 && ((Short)value) <= 255){
						value = ((Short)value).byteValue();
						javaType = JavaType.of(value);
						jdbcType = javaType.getJDBCType(SSType.UNKNOWN, jdbcType);
					}
					// value falls outside tinyint range. Throw an error if the user intends to send as tinyint.
					else
					{
						// This is for cases like setObject(1, Short.valueOf("-1"), java.sql.Types.TINYINT);
						if (JDBCType.TINYINT == jdbcType)
						{
							MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));	
							Object[] msgArgs = {javaType.toString().toLowerCase(Locale.ENGLISH), jdbcType.toString().toLowerCase(Locale.ENGLISH)};
							throw new SQLServerException(form.format(msgArgs), null);							
						}
					}
				}
			}
		}

		//forceEncryption is true, shouldhonorae is false
		if((true == forceEncrypt) && (false == Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, con))){

			MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ForceEncryptionTrue_HonorAEFalse"));
			Object[] msgArgs = {parameterIndex, userSQL};
			SQLServerException.makeFromDriverError(con, this, form.format(msgArgs), null, true);

		}

		// DateTimeOffset is not supported with SQL Server versions earlier than Katmai
		if ((JDBCType.DATETIMEOFFSET == jdbcType || JavaType.DATETIMEOFFSET == javaType) &&
				!con.isKatmaiOrLater())
		{
			throw new SQLServerException(
					SQLServerException.getErrString("R_notSupported"),
					SQLState.DATA_EXCEPTION_NOT_SPECIFIC,
					DriverError.NOT_SET,
					null);
		}

		if( JavaType.TVP == javaType )
		{
			TVP tvpValue = null;
			if (null == value)
			{
				tvpValue = new TVP (tvpName);
			}
			else if (value instanceof SQLServerDataTable)
			{
				tvpValue = new TVP (tvpName, (SQLServerDataTable) value);
			}
			else if (value instanceof ResultSet)
			{
				// if ResultSet and PreparedStatemet/CallableStatement are created from same connection object
		        // with property SelectMethod=cursor, TVP is not supported 
				if(con.getSelectMethod().equalsIgnoreCase("cursor") && (value instanceof SQLServerResultSet))
				{
					SQLServerStatement stmt = (SQLServerStatement) ((SQLServerResultSet)value).getStatement();

					if(con.equals(stmt.connection))
					{
						throw new SQLServerException(
								SQLServerException.getErrString("R_invalidServerCursorForTVP"),
								null);
					}
				}
				
				tvpValue = new TVP (tvpName, (ResultSet) value);
			}
			else if (value instanceof ISQLServerDataRecord)
			{
				tvpValue = new TVP (tvpName, (ISQLServerDataRecord) value);
			}
			else
			{
				MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_TVPInvalidValue"));
				Object[] msgArgs = {parameterIndex};
				throw new SQLServerException(form.format(msgArgs), null);
			}

			if (!tvpValue.isNull() &&  (0 == tvpValue.getTVPColumnCount()))
			{
				throw new SQLServerException(
						SQLServerException.getErrString("R_TVPEmptyMetadata"),
						null);
			}
			name = (tvpValue).getTVPName();
			schemaName= tvpValue.getOwningSchemaNameTVP();

			value = tvpValue;
		}

		// setting JDBCType and exact length needed for AE stored procedure
		if (shouldHonorAEForParameter)
		{
			setForceEncryption(forceEncrypt);

			//set it if it is not output parameter or jdbcTypeSetByUser is null
			if(!(this.isOutput() && this.jdbcTypeSetByUser != null)){
				setJdbcTypeSetByUser(jdbcType);
			}

			//skip it if is (character types or binary type) & is output parameter && value is already set,
			if( (! (jdbcType.isTextual() || jdbcType.isBinary()) ) || !(this.isOutput()) || (this.valueLength == 0)  ){
				this.valueLength = Util.getValueLengthBaseOnJavaType(value, javaType, precision, scale, jdbcType);
			}

			if(null != scale){
				this.outScale = scale;
			}
		}

		// sendStringParametersAsUnicode
		// If set to true, this connection property tells the driver to send textual parameters
		// to the server as Unicode rather than MBCS.  This is accomplished here by re-tagging
		// the value with the appropriate corresponding Unicode type.
		if (con.sendStringParametersAsUnicode() &&
				(JavaType.STRING == javaType ||
				JavaType.READER == javaType ||
				JavaType.CLOB == javaType))
		{
			jdbcType = getSSPAUJDBCType(jdbcType);
		}

		DTV newDTV = new DTV();
		newDTV.setValue(con.getDatabaseCollation(), jdbcType, value, javaType, streamSetterArgs, calendar, scale, con, forceEncrypt);
		
		if(!con.sendStringParametersAsUnicode()){
			newDTV.sendStringParametersAsUnicode = false;
		}
		
		inputDTV = setterDTV = newDTV;
	}

	boolean isNull()
	{
		if(null != getterDTV)
			return getterDTV.isNull();

		return false;
	}

	boolean isValueGotten()
	{
		return (null!=getterDTV)? (true):(false);

	}

	Object getValue(JDBCType jdbcType, InputStreamGetterArgs getterArgs, Calendar cal, TDSReader tdsReader) throws SQLServerException
	{
		if(null == getterDTV)
			getterDTV = new DTV();    	

		deriveTypeInfo(tdsReader);
		// If the parameter is not encrypted or column encryption is turned off (either at connection or 
		// statement level), cryptoMeta would be null.
		return getterDTV.getValue(jdbcType, outScale, getterArgs, cal, typeInfo, cryptoMeta, tdsReader);
	}

	int getInt(TDSReader tdsReader) throws SQLServerException
	{
		Integer value = (Integer) getValue(JDBCType.INTEGER, null, null, tdsReader);
		return null != value ? value.intValue() : 0;
	}

	/**
	 * DTV execute op to determine the parameter type definition.
	 */
	final class GetTypeDefinitionOp extends DTVExecuteOp
	{
		private static final String NVARCHAR_MAX = "nvarchar(max)";
		private static final String NVARCHAR_4K = "nvarchar(4000)";
		private static final String NTEXT = "ntext";

		private static final String VARCHAR_MAX = "varchar(max)";
		private static final String VARCHAR_8K = "varchar(8000)";
		private static final String TEXT = "text";

		private static final String VARBINARY_MAX = "varbinary(max)";
		private static final String VARBINARY_8K = "varbinary(8000)";
		private static final String IMAGE = "image";

		private final Parameter param;
		private final SQLServerConnection con;

		GetTypeDefinitionOp(
				Parameter param,
				SQLServerConnection con)
		{
			this.param = param;
			this.con = con;
		}

		private void setTypeDefinition(DTV dtv)
		{
			switch (dtv.getJdbcType())
			{
			case TINYINT:
				param.typeDefinition = SSType.TINYINT.toString();
				break;

			case SMALLINT:
				param.typeDefinition = SSType.SMALLINT.toString();
				break;

			case INTEGER:
				param.typeDefinition = SSType.INTEGER.toString();
				break;

			case BIGINT:
				param.typeDefinition = SSType.BIGINT.toString();
				break;
				
			case REAL:
				// sp_describe_parameter_encryption must be queried as real for AE
				if(param.shouldHonorAEForParameter && 
					(null!=jdbcTypeSetByUser) &&
					!(null == param.getCryptoMetadata() && param.renewDefinition))
				{
					/*
					 *  This means AE is ON in the connection, and
					 *  (1) this is either the first round to SQL Server to get encryption meta data, or 
					 *  (2) this is the second round of renewing meta data and parameter is encrypted
					 *  In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
					 */
					param.typeDefinition = SSType.REAL.toString();
					break;
				}
			case FLOAT:
			case DOUBLE:
				param.typeDefinition = SSType.FLOAT.toString();
				break;

			case DECIMAL:
			case NUMERIC:
				// First, bound the scale by the maximum allowed by SQL Server
				if (scale > SQLServerConnection.maxDecimalPrecision)
					scale = SQLServerConnection.maxDecimalPrecision;

				// Next, prepare with the largest of:
				// - the value's scale (initial value, as limited above)
				// - the specified input scale (if any)
				// - the registered output scale
				Integer inScale = dtv.getScale();
				if (null != inScale && scale < inScale.intValue())
					scale = inScale.intValue();

				if (param.isOutput() && scale < param.getOutScale())
					scale = param.getOutScale();

				if(param.shouldHonorAEForParameter && 
					(null!=jdbcTypeSetByUser) &&
					!(null == param.getCryptoMetadata() && param.renewDefinition))
				{
					/*
					 *  This means AE is ON in the connection, and
					 *  (1) this is either the first round to SQL Server to get encryption meta data, or 
					 *  (2) this is the second round of renewing meta data and parameter is encrypted
					 *  In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
					 */
					if(0 == valueLength){
						//for prepared statement and callable statement, There are only two cases where valueLength is 0:
						//1. when the parameter is output parameter
						//2. for input parameter, the value is null
						//so, here, if the decimal parameter is encrypted and it is null and it is not outparameter 
						//then we set precision as the default precision instead of max precision 
						if(!isOutput()){
							param.typeDefinition = "decimal(" + SQLServerConnection.defaultDecimalPrecision + ", " + scale + ")";
						}
					}
					else{						
						if(SQLServerConnection.defaultDecimalPrecision >= valueLength){
							param.typeDefinition = "decimal("+SQLServerConnection.defaultDecimalPrecision+","+scale+")";

							if(SQLServerConnection.defaultDecimalPrecision < (valueLength + scale)){
								param.typeDefinition = "decimal(" + (SQLServerConnection.defaultDecimalPrecision + scale) + ","+scale+")";
							}
						}
						else{
							param.typeDefinition = "decimal("+SQLServerConnection.maxDecimalPrecision+","+scale+")";
						}
					}
					
					if(isOutput()){
						param.typeDefinition = "decimal(" + SQLServerConnection.maxDecimalPrecision + ", " + scale + ")";
					}
					
					if(userProvidesPrecision){
						param.typeDefinition = "decimal("+valueLength+","+scale+")";
					}
				}
				else                
					param.typeDefinition = "decimal("+SQLServerConnection.maxDecimalPrecision+","+scale+")";

				break;

			case MONEY:
				param.typeDefinition = SSType.MONEY.toString();
				break;
			case SMALLMONEY:
				param.typeDefinition = SSType.MONEY.toString();
				
				if(param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)){
					param.typeDefinition = SSType.SMALLMONEY.toString();
				}
				
				break;
			case BIT:
			case BOOLEAN:
				param.typeDefinition = SSType.BIT.toString();
				break;

			case LONGVARBINARY:
			case BLOB:
				param.typeDefinition = VARBINARY_MAX;
				break;

			case BINARY:
			case VARBINARY:
				// To avoid the server side cost of re-preparing, once a "long" type, always a "long" type...
				if (VARBINARY_MAX == param.typeDefinition || IMAGE == param.typeDefinition)
					break;
				if(param.shouldHonorAEForParameter && 
					(null!=jdbcTypeSetByUser) &&
					!(null == param.getCryptoMetadata() && param.renewDefinition))
				{
					/*
					 *  This means AE is ON in the connection, and
					 *  (1) this is either the first round to SQL Server to get encryption meta data, or 
					 *  (2) this is the second round of renewing meta data and parameter is encrypted
					 *  In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
					 */
					if(0 == valueLength){
						param.typeDefinition = "varbinary";
					}
					else{
						param.typeDefinition = "varbinary("+valueLength+")";
					}
					
					if(JDBCType.LONGVARBINARY== jdbcTypeSetByUser){
						param.typeDefinition = VARBINARY_MAX;
					}
				}
				else
					param.typeDefinition = VARBINARY_8K;
				break;

			case DATE:
				// Bind DATE values to pre-Katmai servers as DATETIME (which has no DATE-only type).
				param.typeDefinition = con.isKatmaiOrLater() ? SSType.DATE.toString() : SSType.DATETIME.toString();
				break;

			case TIME:
				if(param.shouldHonorAEForParameter 
						&& !(null == param.getCryptoMetadata() && param.renewDefinition)
						){

					/*
					 *  This means AE is ON in the connection, and
					 *  (1) this is either the first round to SQL Server to get encryption meta data, or 
					 *  (2) this is the second round of renewing meta data and parameter is encrypted
					 *  In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
					 */
					
					if(userProvidesScale){
						param.typeDefinition = (SSType.TIME.toString() + "(" + outScale + ")");
					}
					else{
						param.typeDefinition =
								param.typeDefinition = SSType.TIME.toString() + "(" + valueLength + ")";
					}
				}
				else{
					param.typeDefinition =
							con.getSendTimeAsDatetime() ?
									SSType.DATETIME.toString() :
										SSType.TIME.toString();
				}
				break;

			case TIMESTAMP:
				// Bind TIMESTAMP values to pre-Katmai servers as DATETIME.  Bind TIMESTAMP values to
				// Katmai and later servers as DATETIME2 to take advantage of increased precision.
				if(param.shouldHonorAEForParameter 
						&& !(null == param.getCryptoMetadata() && param.renewDefinition) 
						){					
					/*
					 *  This means AE is ON in the connection, and
					 *  (1) this is either the first round to SQL Server to get encryption meta data, or 
					 *  (2) this is the second round of renewing meta data and parameter is encrypted
					 *  In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
					 */
					if(userProvidesScale){
						param.typeDefinition =
								con.isKatmaiOrLater() ? (SSType.DATETIME2.toString() + "(" + outScale + ")") : (SSType.DATETIME.toString());
					}
					else{
						param.typeDefinition =
								con.isKatmaiOrLater() ? (SSType.DATETIME2.toString() + "(" + valueLength + ")") : SSType.DATETIME.toString();
					}
				}
				else{
					param.typeDefinition =
		                    con.isKatmaiOrLater() ? SSType.DATETIME2.toString() : SSType.DATETIME.toString();
				}
				break;

			case DATETIME:
				//send as Datetime by default
				param.typeDefinition = SSType.DATETIME2.toString();
				
				if(param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)){
					param.typeDefinition = SSType.DATETIME.toString();
				}

				if(!param.shouldHonorAEForParameter){
					//if AE is off and it is output parameter of stored procedure, sent it as datetime2(3)
					//otherwise it returns incorrect milliseconds.
					if(param.isOutput()){
						param.typeDefinition = SSType.DATETIME2.toString() + "(" + outScale + ")";
					}
				}
				else{
					//when AE is on, set it to Datetime by default,
					//However, if column is not encrypted and it is output parameter of stored procedure, 
					//renew it to datetime2(3)
					if(null == param.getCryptoMetadata() && param.renewDefinition){
						if(param.isOutput()){
							param.typeDefinition = SSType.DATETIME2.toString() + "(" + outScale + ")";
						}
						break;
					}
				}
				break;

			case SMALLDATETIME:
				param.typeDefinition = SSType.DATETIME2.toString();
				
				if(param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)){
					param.typeDefinition = SSType.SMALLDATETIME.toString();
				}

				break;

			case TIME_WITH_TIMEZONE:
			case TIMESTAMP_WITH_TIMEZONE:
			case DATETIMEOFFSET:
				if(param.shouldHonorAEForParameter 
						&& !(null == param.getCryptoMetadata() && param.renewDefinition)
						){			
					/*
					 *  This means AE is ON in the connection, and
					 *  (1) this is either the first round to SQL Server to get encryption meta data, or 
					 *  (2) this is the second round of renewing meta data and parameter is encrypted
					 *  In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
					 */					
					if(userProvidesScale){
						param.typeDefinition = SSType.DATETIMEOFFSET.toString() + "(" + outScale + ")";
					}
					else{
						param.typeDefinition = SSType.DATETIMEOFFSET.toString() + "(" + valueLength + ")";
					}
				}
				else{
					 param.typeDefinition = SSType.DATETIMEOFFSET.toString();
				}
				break;

			case LONGVARCHAR:
			case CLOB:
				param.typeDefinition = VARCHAR_MAX;
				break;

			case CHAR:
			case VARCHAR:				
				// To avoid the server side cost of re-preparing, once a "long" type, always a "long" type...
				if (VARCHAR_MAX == param.typeDefinition || TEXT == param.typeDefinition)
					break;

				// Adding for case useColumnEncryption=true & sendStringParametersAsUnicode=false
				if(param.shouldHonorAEForParameter && 
					(null!=jdbcTypeSetByUser) &&
					!(null == param.getCryptoMetadata() && param.renewDefinition))
				{
					/*
					 *  This means AE is ON in the connection, and
					 *  (1) this is either the first round to SQL Server to get encryption meta data, or 
					 *  (2) this is the second round of renewing meta data and parameter is encrypted
					 *  In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
					 */										
					if(0 == valueLength){
						param.typeDefinition = "varchar";
					}
					else{
						param.typeDefinition = "varchar("+valueLength+")";
						
						if( DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength){
							param.typeDefinition = VARCHAR_MAX;
						}
					}
				}
				else
					param.typeDefinition = VARCHAR_8K;
				break;

			case LONGNVARCHAR:
				if(param.shouldHonorAEForParameter &&
					!(null == param.getCryptoMetadata() && param.renewDefinition))
				{
					/*
					 *  This means AE is ON in the connection, and
					 *  (1) this is either the first round to SQL Server to get encryption meta data, or 
					 *  (2) this is the second round of renewing meta data and parameter is encrypted
					 *  In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
					 */										
					if((null!=jdbcTypeSetByUser) && 
							((jdbcTypeSetByUser == JDBCType.VARCHAR) || (jdbcTypeSetByUser == JDBCType.CHAR) || (jdbcTypeSetByUser == JDBCType.LONGVARCHAR))){
						if(0 == valueLength){
							param.typeDefinition = "varchar";
						}
						else if(DataTypes.SHORT_VARTYPE_MAX_BYTES < valueLength){
							param.typeDefinition = VARCHAR_MAX;
						}
						else{
							param.typeDefinition = "varchar("+valueLength+")";
						}
						
						if(jdbcTypeSetByUser == JDBCType.LONGVARCHAR){
							param.typeDefinition = VARCHAR_MAX;
						}
					}
					else if((null!=jdbcTypeSetByUser) 
							&& (jdbcTypeSetByUser == JDBCType.NVARCHAR || jdbcTypeSetByUser == JDBCType.LONGNVARCHAR)){
						if(0 == valueLength){
							param.typeDefinition = "nvarchar";
						}
						else if(DataTypes.SHORT_VARTYPE_MAX_CHARS < valueLength){
							param.typeDefinition = NVARCHAR_MAX;
						}
						else{
							param.typeDefinition = "nvarchar("+valueLength+")";
						}
						
						if(jdbcTypeSetByUser == JDBCType.LONGNVARCHAR){
							param.typeDefinition = NVARCHAR_MAX;
						}
					}
					else{ // used if setNull() is called with java.sql.Types.NCHAR 
						if(0 == valueLength){
							param.typeDefinition = "nvarchar";
						}
						else{
							param.typeDefinition = "nvarchar("+valueLength+")";
							
							if( DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength){
								param.typeDefinition = NVARCHAR_MAX;
							}
						}
					}
					break;	                
				}
				else
					param.typeDefinition = NVARCHAR_MAX;
				break;

			case NCLOB:
				//do not need to check if AE is enabled or not,
				//because NCLOB does not work with it
				param.typeDefinition = NVARCHAR_MAX;
				break;

			case NCHAR:
			case NVARCHAR:
				// To avoid the server side cost of re-preparing, once a "long" type, always a "long" type...
				if (NVARCHAR_MAX == param.typeDefinition || NTEXT == param.typeDefinition)
					break;

				if(param.shouldHonorAEForParameter && 
					!(null == param.getCryptoMetadata() && param.renewDefinition))
				{
					/*
					 *  This means AE is ON in the connection, and
					 *  (1) this is either the first round to SQL Server to get encryption meta data, or 
					 *  (2) this is the second round of renewing meta data and parameter is encrypted
					 *  In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
					 */										
					if((null!=jdbcTypeSetByUser) && 
							((jdbcTypeSetByUser == JDBCType.VARCHAR)|| (jdbcTypeSetByUser == JDBCType.CHAR)
									|| (JDBCType.LONGVARCHAR == jdbcTypeSetByUser))){
						if(0 == valueLength){
							param.typeDefinition = "varchar";
						}
						else{
							param.typeDefinition = "varchar("+valueLength+")";
							
							if( DataTypes.SHORT_VARTYPE_MAX_BYTES < valueLength){
								param.typeDefinition = VARCHAR_MAX;
							}
						}
						
						if(JDBCType.LONGVARCHAR == jdbcTypeSetByUser){
							param.typeDefinition = VARCHAR_MAX;
						}
					}
					else if((null!=jdbcTypeSetByUser) && 
							((jdbcTypeSetByUser == JDBCType.NVARCHAR) || (jdbcTypeSetByUser == JDBCType.NCHAR)
									|| (JDBCType.LONGNVARCHAR == jdbcTypeSetByUser))){
						if(0 == valueLength){
							param.typeDefinition = "nvarchar";
						}
						else{
							param.typeDefinition = "nvarchar("+valueLength+")";

							if( DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength){
								param.typeDefinition = NVARCHAR_MAX;
							}
						}
						
						if(JDBCType.LONGNVARCHAR == jdbcTypeSetByUser){
							param.typeDefinition = NVARCHAR_MAX;
						}
					}
					else{ // used if setNull() is called with java.sql.Types.NCHAR 
						if(0 == valueLength){
							param.typeDefinition = "nvarchar";
						}
						else{
							param.typeDefinition = "nvarchar("+valueLength+")";
							
							if( DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength){
								param.typeDefinition = NVARCHAR_MAX;
							}
						}
					}
					break;	                
				}
				else
					param.typeDefinition = NVARCHAR_4K;
				break;
			case SQLXML:
				param.typeDefinition = SSType.XML.toString();
				break;

			case TVP:
				// definition should contain the TVP name and the keyword READONLY
				String schema = param.schemaName;
				
				if(null != schema){
					param.typeDefinition = "[" + schema + "].[" + param.name + "] READONLY";
				}
				else{
					param.typeDefinition = "[" + param.name + "] READONLY";
				}
				
				break;

			case GUID:
				param.typeDefinition = SSType.GUID.toString();
				break;

			default:
				assert false : "Unexpected JDBC type " + dtv.getJdbcType();
			break;
			}
		}

		void execute(DTV dtv, String strValue) throws SQLServerException
		{
			if (null != strValue && strValue.length() > DataTypes.SHORT_VARTYPE_MAX_CHARS)
				dtv.setJdbcType(JDBCType.LONGNVARCHAR);

			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, Clob clobValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, Byte byteValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, Integer intValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, java.sql.Time timeValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, java.sql.Date dateValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, java.sql.Timestamp timestampValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, java.util.Date utildateValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, java.util.Calendar calendarValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, LocalDate localDateValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, LocalTime localTimeValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, LocalDateTime localDateTimeValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, OffsetTime offsetTimeValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, OffsetDateTime OffsetDateTimeValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, microsoft.sql.DateTimeOffset dtoValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, Float floatValue) throws SQLServerException
		{
			scale = 4;
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, Double doubleValue) throws SQLServerException
		{
			scale = 4;
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, BigDecimal bigDecimalValue) throws SQLServerException
		{
			if (null != bigDecimalValue)
			{
				scale = bigDecimalValue.scale();

				// BigDecimal in JRE 1.5 and later JVMs exposes an implementation detail
				// that allows representation of large values in small space by interpreting
				// a negative value for scale to imply scientific notation (e.g. 1 E 10^n)
				// would have a scale of -n. A BigDecimal value with a negative scale has
				// no fractional component.
				if (scale < 0)
					scale = 0;
			}

			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, Long longValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, java.math.BigInteger bigIntegerValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, Short shortValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, Boolean booleanValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, byte[] byteArrayValue) throws SQLServerException
		{
			if (null != byteArrayValue && byteArrayValue.length > DataTypes.SHORT_VARTYPE_MAX_BYTES)
				dtv.setJdbcType(dtv.getJdbcType().isBinary() ? JDBCType.LONGVARBINARY : JDBCType.LONGVARCHAR);

			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, Blob blobValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, InputStream inputStreamValue) throws SQLServerException
		{
			StreamSetterArgs streamSetterArgs = dtv.getStreamSetterArgs();

			JDBCType jdbcType = dtv.getJdbcType();

			// If the JDBC type is currently a "short" type, then figure out if needs to be bumped up to a "long" type
			if (JDBCType.CHAR == jdbcType ||
					JDBCType.VARCHAR == jdbcType ||
					JDBCType.BINARY == jdbcType ||
					JDBCType.VARBINARY == jdbcType)
			{
				// If we know the length is too long for a "short" type, then convert to a "long" type.
				if (streamSetterArgs.getLength() > DataTypes.SHORT_VARTYPE_MAX_BYTES)
					dtv.setJdbcType(jdbcType.isBinary() ? JDBCType.LONGVARBINARY : JDBCType.LONGVARCHAR);

				// If the length of the value is unknown, then figure out whether it is at least longer
				// than what will fit into a "short" type.
				else if (DataTypes.UNKNOWN_STREAM_LENGTH == streamSetterArgs.getLength())
				{
					byte[] vartypeBytes = new byte[1 + DataTypes.SHORT_VARTYPE_MAX_BYTES];
					BufferedInputStream bufferedStream = new BufferedInputStream(inputStreamValue, vartypeBytes.length);

					int bytesRead = 0;

					try
					{
						bufferedStream.mark(vartypeBytes.length);

						bytesRead = bufferedStream.read(vartypeBytes, 0, vartypeBytes.length);

						if (-1 == bytesRead)
							bytesRead = 0;

						bufferedStream.reset();
					}
					catch (IOException e)
					{
						MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
						Object[] msgArgs = {e.toString()};
						SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), "", true);
					}

					dtv.setValue(bufferedStream, JavaType.INPUTSTREAM);

					// If the stream is longer than what can fit into the "short" type, then use the "long" type instead.
					// Otherwise, we know the exact stream length since we reached end of stream before reading SHORT_VARTYPE_MAX_BYTES + 1
					// bytes.  So adjust the setter args to reflect the known length to avoid unnecessarily copying the
					// stream again in SendByRPCOp.
					if (bytesRead > DataTypes.SHORT_VARTYPE_MAX_BYTES)
						dtv.setJdbcType(jdbcType.isBinary() ? JDBCType.LONGVARBINARY : JDBCType.LONGVARCHAR);
					else
						streamSetterArgs.setLength(bytesRead);
				}
			}

			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, Reader readerValue) throws SQLServerException
		{
			// If the JDBC type is currently a "short" type, then figure out if needs to be bumped up to a "long" type
			if (JDBCType.NCHAR == dtv.getJdbcType() || JDBCType.NVARCHAR == dtv.getJdbcType())
			{
				StreamSetterArgs streamSetterArgs = dtv.getStreamSetterArgs();

				// If we know the length is too long for a "short" type, then convert to a "long" type.
				if (streamSetterArgs.getLength() > DataTypes.SHORT_VARTYPE_MAX_CHARS)
					dtv.setJdbcType(JDBCType.LONGNVARCHAR);

				// If the length of the value is unknown, then figure out whether it is at least longer
				// than what will fit into a "short" type.
				else if (DataTypes.UNKNOWN_STREAM_LENGTH == streamSetterArgs.getLength())
				{
					char[] vartypeChars = new char[1 + DataTypes.SHORT_VARTYPE_MAX_CHARS];
					BufferedReader bufferedReader = new BufferedReader(readerValue, vartypeChars.length);

					int charsRead = 0;

					try
					{
						bufferedReader.mark(vartypeChars.length);

						charsRead = bufferedReader.read(vartypeChars, 0, vartypeChars.length);

						if (-1 == charsRead)
							charsRead = 0;

						bufferedReader.reset();
					}
					catch (IOException e)
					{
						MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
						Object[] msgArgs = {e.toString()};
						SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), "", true);
					}

					dtv.setValue(bufferedReader, JavaType.READER);

					if (charsRead > DataTypes.SHORT_VARTYPE_MAX_CHARS)
						dtv.setJdbcType(JDBCType.LONGNVARCHAR);
					else
						streamSetterArgs.setLength(charsRead);
				}
			}

			setTypeDefinition(dtv);
		}
		void execute(DTV dtv, SQLServerSQLXML xmlValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

		void execute(DTV dtv, com.microsoft.sqlserver.jdbc.TVP tvpValue) throws SQLServerException
		{
			setTypeDefinition(dtv);
		}

	}

	/**
	 * Returns a string used to define the parameter type for the server;
	 * null if no value for the parameter has been set or registered.
	 */
	String getTypeDefinition(SQLServerConnection con, TDSReader tdsReader) throws SQLServerException
	{
		if (null == inputDTV)
			return null;

		inputDTV.executeOp(new GetTypeDefinitionOp(this, con));
		return typeDefinition;
	}

	void sendByRPC(
			TDSWriter tdsWriter,
			SQLServerConnection conn) throws SQLServerException
	{
		assert null != inputDTV : "Parameter was neither set nor registered";

		try
		{
			inputDTV.sendCryptoMetaData(this.cryptoMeta, tdsWriter);
			inputDTV.jdbcTypeSetByUser(getJdbcTypeSetByUser(), getValueLength());
			inputDTV.sendByRPC(
					name,
					null,
					conn.getDatabaseCollation(),
					valueLength,
					isOutput()?outScale:scale,
							isOutput(),
							tdsWriter,
							conn);
		}
		finally
		{
			// reset the cryptoMeta in IOBuffer
			inputDTV.sendCryptoMetaData(null, tdsWriter);
		}
		// Per JDBC spec:
		// "If in the execution of a PreparedStatement object, the JDBC driver reads values set
		// for the parameter markers by the methods setAsciiStream, setBinaryStream, setCharacterStream,
		// setNCharacterStream, or setUnicodeStream, those parameters must be reset prior to the next
		// execution of the PreparedStatement object otherwise a SQLException will be thrown."
		//
		// Clear the input and setter DTVs to relinquish their hold on the stream resource and ensure
		// that the next call to execute will throw a SQLException (from getTypeDefinitionOp).
		// Don't clear the registered output DTV so that the parameter will still be an OUT (IN/OUT) parameter.
		if (JavaType.INPUTSTREAM == inputDTV.getJavaType() ||
				JavaType.READER == inputDTV.getJavaType())
		{
			inputDTV = setterDTV = null;
		}
	}
	JDBCType getJdbcTypeSetByUser() {
		return jdbcTypeSetByUser;
	}
	void setJdbcTypeSetByUser(JDBCType jdbcTypeSetByUser) {
		this.jdbcTypeSetByUser = jdbcTypeSetByUser;
	}
	int getValueLength() {
		return valueLength;
	}
	void setValueLength(int valueLength) {
		this.valueLength = valueLength;
		userProvidesPrecision = true;
	}
	boolean getForceEncryption() {
		return forceEncryption;
	}
	void setForceEncryption(boolean forceEncryption) {
		this.forceEncryption = forceEncryption;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy