diff --git a/pom.xml b/pom.xml index 3052d34fd..385bddfad 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse - xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance NTLM - - - - - - - For tests using NTLM Authentication mode (excluded by default) - kerberos - - - - - For tests using Kerberos authentication (excluded by default) + kerberos - - - - - For tests using Kerberos authentication (excluded by default) reqExternalSetup - For tests requiring external setup (excluded by default) clientCertAuth - - For tests requiring client certificate authentication setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 9e239c5d2..0039457dc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -466,6 +466,7 @@ static final String getEncryptionLevel(int level) { final static int COLINFO_STATUS_DIFFERENT_NAME = 0x20; final static int MAX_FRACTIONAL_SECONDS_SCALE = 7; + final static int DEFAULT_FRACTIONAL_SECONDS_SCALE = 3; final static Timestamp MAX_TIMESTAMP = Timestamp.valueOf("2079-06-06 23:59:59"); final static Timestamp MIN_TIMESTAMP = Timestamp.valueOf("1900-01-01 00:00:00"); @@ -4788,7 +4789,7 @@ void writeVMaxHeader(long headerLength, boolean isNull, SQLCollation collation) * Utility for internal writeRPCString calls */ void writeRPCStringUnicode(String sValue) throws SQLServerException { - writeRPCStringUnicode(null, sValue, false, null); + writeRPCStringUnicode(null, sValue, false, null, false); } /** @@ -4803,8 +4804,8 @@ void writeRPCStringUnicode(String sValue) throws SQLServerException { * @param collation * the collation of the data value */ - void writeRPCStringUnicode(String sName, String sValue, boolean bOut, - SQLCollation collation) throws SQLServerException { + void writeRPCStringUnicode(String sName, String sValue, boolean bOut, SQLCollation collation, + boolean isNonPLP) throws SQLServerException { boolean bValueNull = (sValue == null); int nValueLen = bValueNull ? 0 : (2 * sValue.length()); // Textual RPC requires a collation. If none is provided, as is the case when @@ -4816,10 +4817,9 @@ void writeRPCStringUnicode(String sName, String sValue, boolean bOut, * Use PLP encoding if either OUT params were specified or if the user query exceeds * DataTypes.SHORT_VARTYPE_MAX_BYTES */ - if (nValueLen > DataTypes.SHORT_VARTYPE_MAX_BYTES || bOut) { + if ((nValueLen > DataTypes.SHORT_VARTYPE_MAX_BYTES || bOut) && !isNonPLP) { writeRPCNameValType(sName, bOut, TDSType.NVARCHAR); - // Handle Yukon v*max type header here. writeVMaxHeader(nValueLen, // Length bValueNull, // Is null? collation); @@ -5418,7 +5418,6 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException { // Use PLP encoding on Yukon and later with long values if (!isShortValue) // PLP { - // Handle Yukon v*max type header here. writeShort((short) 0xFFFF); con.getDatabaseCollation().writeCollation(this); } else // non PLP @@ -5436,7 +5435,6 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException { isShortValue = pair.getValue().precision <= DataTypes.SHORT_VARTYPE_MAX_BYTES; // Use PLP encoding on Yukon and later with long values if (!isShortValue) // PLP - // Handle Yukon v*max type header here. writeShort((short) 0xFFFF); else // non PLP writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES); @@ -5571,8 +5569,8 @@ void writeCryptoMetaData() throws SQLServerException { writeByte(cryptoMeta.normalizationRuleVersion); } - void writeRPCByteArray(String sName, byte[] bValue, boolean bOut, JDBCType jdbcType, - SQLCollation collation) throws SQLServerException { + void writeRPCByteArray(String sName, byte[] bValue, boolean bOut, JDBCType jdbcType, SQLCollation collation, + boolean isNonPLP) throws SQLServerException { boolean bValueNull = (bValue == null); int nValueLen = bValueNull ? 0 : bValue.length; boolean isShortValue = (nValueLen <= DataTypes.SHORT_VARTYPE_MAX_BYTES); @@ -5618,8 +5616,7 @@ void writeRPCByteArray(String sName, byte[] bValue, boolean bOut, JDBCType jdbcT writeRPCNameValType(sName, bOut, tdsType); - if (usePLP) { - // Handle Yukon v*max type header here. + if (usePLP && !isNonPLP) { writeVMaxHeader(nValueLen, bValueNull, collation); // Send the data. @@ -6400,7 +6397,6 @@ void writeRPCInputStream(String sName, InputStream stream, long streamLength, bo writeRPCNameValType(sName, bOut, jdbcType.isTextual() ? TDSType.BIGVARCHAR : TDSType.BIGVARBINARY); - // Handle Yukon v*max type header here. writeVMaxHeader(streamLength, false, jdbcType.isTextual() ? collation : null); } @@ -6540,7 +6536,6 @@ void writeRPCReaderUnicode(String sName, Reader re, long reLength, boolean bOut, writeRPCNameValType(sName, bOut, TDSType.NVARCHAR); - // Handle Yukon v*max type header here. writeVMaxHeader( (DataTypes.UNKNOWN_STREAM_LENGTH == reLength) ? DataTypes.UNKNOWN_STREAM_LENGTH : 2 * reLength, // Length // (in @@ -6995,6 +6990,35 @@ final short peekStatusFlag() { return 0; } + final int peekReturnValueStatus() throws SQLServerException { + // Ensure that we have a packet to read from. + if (!ensurePayload()) { + throwInvalidTDS(); + } + + // In order to parse the 'status' value, we need to skip over the following properties in the TDS packet + // payload: TDS token type (1 byte value), ordinal/length (2 byte value), parameter name length value (1 byte value) and + // the number of bytes that make the parameter name (need to be calculated). + // + // 'offset' starts at 4 because tdsTokenType + ordinal/length + parameter name length value is 4 bytes. So, we + // skip 4 bytes immediateley. + int offset = 4; + int paramNameLength = currentPacket.payload[payloadOffset + 3]; + + // Check if parameter name is set. If it's set, it should be > 0. In which case, we add the + // additional bytes to skip. + if (paramNameLength > 0) { + // Each character in unicode is 2 bytes + offset += 2 * paramNameLength; + } + + if (payloadOffset + offset <= currentPacket.payloadLength) { + return currentPacket.payload[payloadOffset + offset] & 0xFF; + } + + return -1; + } + final int readUnsignedByte() throws SQLServerException { // Ensure that we have a packet to read from. if (!ensurePayload()) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java index 314e64435..28e065980 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java @@ -496,5 +496,5 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold, * @param calcBigDecimalScale * A boolean that indicates if the driver should calculate scale from inputted big decimal values. */ - void setCalcBigDecimalScale(boolean computeBigDecimal); + void setCalcBigDecimalScale(boolean calcBigDecimalScale); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java index 1e6238756..af6b1afc1 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java @@ -609,6 +609,25 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource { */ boolean getUseDefaultGSSCredential(); + /** + * Sets whether or not sp_sproc_columns will be used for parameter name lookup. + * + * @param useFlexibleCallableStatements + * When set to false, sp_sproc_columns is not used for parameter name lookup + * in callable statements. This eliminates a round trip to the server but imposes limitations + * on how parameters are set. When set to false, applications must either reference + * parameters by name or by index, not both. Parameters must also be set in the same + * order as the stored procedure definition. + */ + void setUseFlexibleCallableStatements(boolean useFlexibleCallableStatements); + + /** + * Returns whether or not sp_sproc_columns is being used for parameter name lookup. + * + * @return useFlexibleCallableStatements + */ + boolean getUseFlexibleCallableStatements(); + /** * Sets the GSSCredential. * diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index fe0985003..858111ca9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -37,6 +37,8 @@ final class Parameter { // For unencrypted parameters cryptometa will be null. For encrypted parameters it will hold encryption metadata. CryptoMetadata cryptoMeta = null; + boolean isNonPLP = false; + TypeInfo getTypeInfo() { return typeInfo; } @@ -48,6 +50,7 @@ final CryptoMetadata getCryptoMetadata() { private boolean shouldHonorAEForParameter = false; private boolean userProvidesPrecision = false; private boolean userProvidesScale = false; + private boolean isReturnValue = false; // The parameter type definition private String typeDefinition = null; @@ -70,6 +73,60 @@ boolean isOutput() { return null != registeredOutDTV; } + /** + * Returns true/false if the parameter is of return type + * + * @return isReturnValue + */ + boolean isReturnValue() { + return isReturnValue; + } + + /** + * Sets the parameter to be of return type + * + * @param isReturnValue + */ + void setReturnValue(boolean isReturnValue) { + this.isReturnValue = isReturnValue; + } + + /** + * Sets the name of the parameter + * + * @param name + */ + void setName(String name) { + this.name = name; + } + + /** + * Retrieve the name of the parameter + * + * @return + */ + String getName() { + return this.name; + } + + /** + * Returns the `registeredOutDTV` instance of the parameter + * + * @return registeredOutDTV + */ + DTV getRegisteredOutDTV() { + return this.registeredOutDTV; + } + + /** + * Returns the `inputDTV` instance of the parameter + * + * @return inputDTV + */ + DTV getInputDTV() { + return this.inputDTV; + } + // 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. @@ -246,7 +303,7 @@ void setFromReturnStatus(int returnStatus, SQLServerConnection con) throws SQLSe if (null == getterDTV) getterDTV = new DTV(); - getterDTV.setValue(null, JDBCType.INTEGER, returnStatus, JavaType.INTEGER, null, null, null, con, + getterDTV.setValue(null, this.getJdbcType(), returnStatus, JavaType.INTEGER, null, null, null, con, getForceEncryption()); } @@ -387,10 +444,14 @@ boolean isValueGotten() { Object getValue(JDBCType jdbcType, InputStreamGetterArgs getterArgs, Calendar cal, TDSReader tdsReader, SQLServerStatement statement) throws SQLServerException { - if (null == getterDTV) + if (null == getterDTV) { getterDTV = new DTV(); + } + + if (null != tdsReader) { + deriveTypeInfo(tdsReader); + } - 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, statement); @@ -1206,15 +1267,17 @@ String getTypeDefinition(SQLServerConnection con, TDSReader tdsReader) throws SQ return typeDefinition; } - void sendByRPC(TDSWriter tdsWriter, SQLServerStatement statement) throws SQLServerException { + void sendByRPC(TDSWriter tdsWriter, boolean callRPCDirectly, + SQLServerStatement statement) throws SQLServerException { assert null != inputDTV : "Parameter was neither set nor registered"; SQLServerConnection conn = statement.connection; try { + inputDTV.isNonPLP = isNonPLP; inputDTV.sendCryptoMetaData(this.cryptoMeta, tdsWriter); inputDTV.setJdbcTypeSetByUser(getJdbcTypeSetByUser(), getValueLength()); - inputDTV.sendByRPC(name, null, conn.getDatabaseCollation(), valueLength, isOutput() ? outScale : scale, - isOutput(), tdsWriter, statement); + inputDTV.sendByRPC(callRPCDirectly ? name : null, null, conn.getDatabaseCollation(), valueLength, + isOutput() ? outScale : scale, isOutput(), tdsWriter, statement); } finally { // reset the cryptoMeta in IOBuffer inputDTV.sendCryptoMetaData(null, tdsWriter); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java index ee6be2904..07c8969a2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java @@ -73,12 +73,25 @@ public class SQLServerCallableStatement extends SQLServerPreparedStatement imple /** Currently active Stream Note only one stream can be active at a time */ private transient Closeable activeStream; + /** Checks if return values is already accessed in stored procedure */ + private boolean isReturnValueAccessed = false; + /** map */ private Map map = new ConcurrentHashMap<>(); /** atomic integer */ AtomicInteger ai = new AtomicInteger(0); + /** + * Enum to check if the callablestatement is a setter or getter method. This enum is used in method findColumn where + * we get the parameter/column by name + * + */ + enum CallableStatementGetterSetterMethod { + IS_SETTER_METHOD, + IS_GETTER_METHOD + } + /** * Create a new callable statement. * @@ -97,6 +110,7 @@ public class SQLServerCallableStatement extends SQLServerPreparedStatement imple SQLServerCallableStatement(SQLServerConnection connection, String sql, int nRSType, int nRSConcur, SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException { super(connection, sql, nRSType, nRSConcur, stmtColEncSetting); + isReturnValueAccessed = false; } @Override @@ -157,6 +171,11 @@ public void registerOutParameter(int index, int sqlType) throws SQLServerExcepti loggerExternal.exiting(getClassNameLogging(), "registerOutParameter"); } + void registerOutParameterNonPLP(int index, int sqlType) throws SQLServerException { + registerOutParameter(index, sqlType); + inOutParam[index - 1].isNonPLP = true; + } + /** * Locate any output parameter values returned from the procedure call */ @@ -168,13 +187,29 @@ private Parameter getOutParameter(int i) throws SQLServerException { processResults(); // if this item has been indexed already leave! - if (inOutParam[i - 1] == lastParamAccessed || inOutParam[i - 1].isValueGotten()) + if (inOutParam[i - 1] == lastParamAccessed || inOutParam[i - 1].isValueGotten()) { + // if it is a return value, increment the nOutParamsAssigned. Checking for isCursorable here is because the + // driver is executing + // the stored procedure for cursorable ones differently ( calling sp_cursorexecute r sp_cursorprepexec. + if (bReturnValueSyntax && inOutParam[i - 1].isValueGotten() && inOutParam[i - 1].isReturnValue() + && !isReturnValueAccessed && !isCursorable(executeMethod) && !isTVPType + && callRPCDirectly(inOutParam)) { + nOutParamsAssigned++; + isReturnValueAccessed = true; + } return inOutParam[i - 1]; + } + + if (inOutParam[i - 1].isReturnValue() && bReturnValueSyntax && !isCursorable(executeMethod) && !isTVPType + && returnValueStatus != userDefinedFunctionReturnStatus) { + return inOutParam[i - 1]; + } // Skip OUT parameters (buffering them as we go) until we // reach the one we're looking for. - while (outParamIndex != i - 1) + while (outParamIndex != i - 1) { skipOutParameters(1, false); + } return inOutParam[i - 1]; } @@ -226,18 +261,24 @@ final void processOutParameters() throws SQLServerException { } // Next, if there are any unindexed parameters left then discard them too. - assert nOutParamsAssigned <= nOutParams; - if (nOutParamsAssigned < nOutParams) - skipOutParameters(nOutParams - nOutParamsAssigned, true); - - // Finally, skip the last-indexed parameter. If there were no unindexed parameters - // in the previous step, then this is the last-indexed parameter left from the first - // step. If we skipped unindexed parameters in the previous step, then this is the - // last-indexed parameter left at the end of that step. - if (outParamIndex >= 0) { - inOutParam[outParamIndex].skipValue(resultsReader(), true); - inOutParam[outParamIndex].resetOutputValue(); - outParamIndex = -1; + if (nOutParamsAssigned <= nOutParams) { + if (bReturnValueSyntax && (nOutParamsAssigned == 0) && !isCursorable(executeMethod)) { + nOutParamsAssigned++; + } + + if (nOutParamsAssigned < nOutParams) { + skipOutParameters(nOutParams - nOutParamsAssigned, true); + } + + // Finally, skip the last-indexed parameter. If there were no unindexed parameters + // in the previous step, then this is the last-indexed parameter left from the first + // step. If we skipped unindexed parameters in the previous step, then this is the + // last-indexed parameter left at the end of that step. + if (outParamIndex >= 0) { + inOutParam[outParamIndex].skipValue(resultsReader(), true); + inOutParam[outParamIndex].resetOutputValue(); + outParamIndex = -1; + } } } @@ -308,60 +349,75 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException { OutParamHandler outParamHandler = new OutParamHandler(); + if (bReturnValueSyntax && (nOutParamsAssigned == 0) && !isCursorable(executeMethod) && !isTVPType + && callRPCDirectly(inOutParam) && returnValueStatus != userDefinedFunctionReturnStatus) { + nOutParamsAssigned++; + } + // Index the application OUT parameters - assert numParamsToSkip <= nOutParams - nOutParamsAssigned; - for (int paramsSkipped = 0; paramsSkipped < numParamsToSkip; ++paramsSkipped) { - // Discard the last-indexed parameter by skipping over it and - // discarding the value if it is no longer needed. - if (-1 != outParamIndex) { - inOutParam[outParamIndex].skipValue(resultsReader(), discardValues); - if (discardValues) - inOutParam[outParamIndex].resetOutputValue(); - } + if (numParamsToSkip <= nOutParams - nOutParamsAssigned) { + for (int paramsSkipped = 0; paramsSkipped < numParamsToSkip; ++paramsSkipped) { + // Discard the last-indexed parameter by skipping over it and + // discarding the value if it is no longer needed. + if (-1 != outParamIndex) { + inOutParam[outParamIndex].skipValue(resultsReader(), discardValues); + if (discardValues) { + inOutParam[outParamIndex].resetOutputValue(); + } + } - // Look for the next parameter value in the response. - outParamHandler.reset(); - TDSParser.parse(resultsReader(), outParamHandler); - - // If we don't find it, then most likely the server encountered some error that - // was bad enough to halt statement execution before returning OUT params, but - // not necessarily bad enough to close the connection. - if (!outParamHandler.foundParam()) { - // If we were just going to discard the OUT parameters we found anyway, - // then it's no problem that we didn't find any of them. For exmaple, - // when we are closing or reexecuting this CallableStatement (that is, - // calling in through processResponse), we don't care that execution - // failed to return the OUT parameters. - if (discardValues) - break; - - // If we were asked to retain the OUT parameters as we skip past them, - // then report an error if we did not find any. - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueNotSetForParameter")); - Object[] msgArgs = {outParamIndex + 1}; - SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false); - } + // Look for the next parameter value in the response. + outParamHandler.reset(); + TDSParser.parse(resultsReader(), outParamHandler); + + // If we don't find it, then most likely the server encountered some error that + // was bad enough to halt statement execution before returning OUT params, but + // not necessarily bad enough to close the connection. + if (!outParamHandler.foundParam()) { + // If we were just going to discard the OUT parameters we found anyway, + // then it's no problem that we didn't find any of them. For exmaple, + // when we are closing or reexecuting this CallableStatement (that is, + // calling in through processResponse), we don't care that execution + // failed to return the OUT parameters. + if (discardValues) + break; + + // If we were asked to retain the OUT parameters as we skip past them, + // then report an error if we did not find any. + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_valueNotSetForParameter")); + Object[] msgArgs = {outParamIndex + 1}; + SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false); + } + + // In Yukon and later, large Object output parameters are reordered to appear at + // the end of the stream. First group of small parameters is sent, followed by + // group of large output parameters. There is no reordering within the groups. + + // Note that parameter ordinals are 0-indexed and that the return status is not + // considered to be an output parameter. + outParamIndex = outParamHandler.srv.getOrdinalOrLength(); + + if (bReturnValueSyntax && !isCursorable(executeMethod) && !isTVPType && callRPCDirectly(inOutParam) + && returnValueStatus != userDefinedFunctionReturnStatus) { + outParamIndex++; + } else { + // Statements need to have their out param indices adjusted by the number + // of sp_[cursor][prep]exec params. + outParamIndex -= outParamIndexAdjustment; + } - // In Yukon and later, large Object output parameters are reordered to appear at - // the end of the stream. First group of small parameters is sent, followed by - // group of large output parameters. There is no reordering within the groups. - - // Note that parameter ordinals are 0-indexed and that the return status is not - // considered to be an output parameter. - outParamIndex = outParamHandler.srv.getOrdinalOrLength(); - - // Statements need to have their out param indices adjusted by the number - // of sp_[cursor][prep]exec params. - outParamIndex -= outParamIndexAdjustment; - if ((outParamIndex < 0 || outParamIndex >= inOutParam.length) || (!inOutParam[outParamIndex].isOutput())) { - if (getStatementLogger().isLoggable(java.util.logging.Level.INFO)) { - getStatementLogger().info(toString() + " Unexpected outParamIndex: " + outParamIndex - + "; adjustment: " + outParamIndexAdjustment); + if ((outParamIndex < 0 || outParamIndex >= inOutParam.length) + || (!inOutParam[outParamIndex].isOutput())) { + if (getStatementLogger().isLoggable(java.util.logging.Level.INFO)) { + getStatementLogger().info(toString() + " Unexpected outParamIndex: " + outParamIndex + + "; adjustment: " + outParamIndexAdjustment); + } + connection.throwInvalidTDS(); } - connection.throwInvalidTDS(); - } - ++nOutParamsAssigned; + ++nOutParamsAssigned; + } } } @@ -414,6 +470,10 @@ private Parameter getterGetParam(int index) throws SQLServerException { // Check for valid index if (index < 1 || index > inOutParam.length) { + if (!connection.getUseFlexibleCallableStatements()) { + SQLServerException.makeFromDriverError(connection, this, + SQLServerException.getErrString("R_unknownOutputParameter"), SQLSTATE_07009, false); + } MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidOutputParameter")); Object[] msgArgs = {index}; SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), SQLSTATE_07009, false); @@ -444,7 +504,13 @@ private Parameter getterGetParam(int index) throws SQLServerException { } private Object getValue(int parameterIndex, JDBCType jdbcType) throws SQLServerException { - return getterGetParam(parameterIndex).getValue(jdbcType, null, null, resultsReader(), this); + Parameter param = getterGetParam(parameterIndex); + if (!param.isValueGotten() || !param.isReturnValue()) { + return param.getValue(jdbcType, null, null, resultsReader(), this); + } else { + // if we have already retrieved the value, we have the typeInfo and we do not need to get it again + return param.getValue(param.getJdbcType(), null, null, null, this); + } } private Object getValue(int parameterIndex, JDBCType jdbcType, Calendar cal) throws SQLServerException { @@ -487,7 +553,8 @@ public int getInt(int index) throws SQLServerException { public int getInt(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getInt", parameterName); checkClosed(); - Integer value = (Integer) getValue(findColumn(parameterName), JDBCType.INTEGER); + Integer value = (Integer) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.INTEGER); loggerExternal.exiting(getClassNameLogging(), "getInt", value); return null != value ? value : 0; } @@ -510,7 +577,8 @@ public String getString(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getString", parameterName); checkClosed(); String value = null; - Object objectValue = getValue(findColumn(parameterName), JDBCType.CHAR); + Object objectValue = getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.CHAR); if (null != objectValue) { value = objectValue.toString(); } @@ -531,7 +599,8 @@ public final String getNString(int parameterIndex) throws SQLException { public final String getNString(String parameterName) throws SQLException { loggerExternal.entering(getClassNameLogging(), "getNString", parameterName); checkClosed(); - String value = (String) getValue(findColumn(parameterName), JDBCType.NCHAR); + String value = (String) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.NCHAR); loggerExternal.exiting(getClassNameLogging(), "getNString", value); return value; } @@ -561,7 +630,8 @@ public BigDecimal getBigDecimal(String parameterName, int scale) throws SQLServe if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "getBigDecimal", new Object[] {parameterName, scale}); checkClosed(); - BigDecimal value = (BigDecimal) getValue(findColumn(parameterName), JDBCType.DECIMAL); + BigDecimal value = (BigDecimal) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DECIMAL); if (null != value) value = value.setScale(scale, BigDecimal.ROUND_DOWN); loggerExternal.exiting(getClassNameLogging(), "getBigDecimal", value); @@ -581,7 +651,8 @@ public boolean getBoolean(int index) throws SQLServerException { public boolean getBoolean(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getBoolean", parameterName); checkClosed(); - Boolean value = (Boolean) getValue(findColumn(parameterName), JDBCType.BIT); + Boolean value = (Boolean) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.BIT); loggerExternal.exiting(getClassNameLogging(), "getBoolean", value); return null != value ? value : false; } @@ -600,7 +671,8 @@ public byte getByte(int index) throws SQLServerException { public byte getByte(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getByte", parameterName); checkClosed(); - Short shortValue = (Short) getValue(findColumn(parameterName), JDBCType.TINYINT); + Short shortValue = (Short) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.TINYINT); byte byteValue = (null != shortValue) ? shortValue.byteValue() : 0; loggerExternal.exiting(getClassNameLogging(), "getByte", byteValue); return byteValue; @@ -619,7 +691,8 @@ public byte[] getBytes(int index) throws SQLServerException { public byte[] getBytes(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getBytes", parameterName); checkClosed(); - byte[] value = (byte[]) getValue(findColumn(parameterName), JDBCType.BINARY); + byte[] value = (byte[]) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.BINARY); loggerExternal.exiting(getClassNameLogging(), "getBytes", value); return value; } @@ -637,7 +710,8 @@ public Date getDate(int index) throws SQLServerException { public Date getDate(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getDate", parameterName); checkClosed(); - java.sql.Date value = (java.sql.Date) getValue(findColumn(parameterName), JDBCType.DATE); + java.sql.Date value = (java.sql.Date) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DATE); loggerExternal.exiting(getClassNameLogging(), "getDate", value); return value; } @@ -657,7 +731,8 @@ public Date getDate(String parameterName, Calendar cal) throws SQLServerExceptio if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "getDate", new Object[] {parameterName, cal}); checkClosed(); - java.sql.Date value = (java.sql.Date) getValue(findColumn(parameterName), JDBCType.DATE, cal); + java.sql.Date value = (java.sql.Date) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DATE); loggerExternal.exiting(getClassNameLogging(), "getDate", value); return value; } @@ -675,7 +750,8 @@ public double getDouble(int index) throws SQLServerException { public double getDouble(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getDouble", parameterName); checkClosed(); - Double value = (Double) getValue(findColumn(parameterName), JDBCType.DOUBLE); + Double value = (Double) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.DOUBLE); loggerExternal.exiting(getClassNameLogging(), "getDouble", value); return null != value ? value : 0; } @@ -691,10 +767,10 @@ public float getFloat(int index) throws SQLServerException { @Override public float getFloat(String parameterName) throws SQLServerException { - loggerExternal.entering(getClassNameLogging(), "getFloat", parameterName); checkClosed(); - Float value = (Float) getValue(findColumn(parameterName), JDBCType.REAL); + Float value = (Float) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.REAL); loggerExternal.exiting(getClassNameLogging(), "getFloat", value); return null != value ? value : 0; } @@ -713,7 +789,8 @@ public long getLong(int index) throws SQLServerException { public long getLong(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getLong", parameterName); checkClosed(); - Long value = (Long) getValue(findColumn(parameterName), JDBCType.BIGINT); + Long value = (Long) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.BIGINT); loggerExternal.exiting(getClassNameLogging(), "getLong", value); return null != value ? value : 0; } @@ -826,7 +903,7 @@ public T getObject(int index, Class type) throws SQLException { public Object getObject(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getObject", parameterName); checkClosed(); - int parameterIndex = findColumn(parameterName); + int parameterIndex = findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD); Object value = getValue(parameterIndex, null != getterGetParam(parameterIndex).getJdbcTypeSetByUser() ? getterGetParam(parameterIndex) .getJdbcTypeSetByUser() : getterGetParam(parameterIndex).getJdbcType()); @@ -838,7 +915,7 @@ public Object getObject(String parameterName) throws SQLServerException { public T getObject(String parameterName, Class type) throws SQLException { loggerExternal.entering(getClassNameLogging(), "getObject", parameterName); checkClosed(); - int parameterIndex = findColumn(parameterName); + int parameterIndex = findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD); T value = getObject(parameterIndex, type); loggerExternal.exiting(getClassNameLogging(), "getObject", value); return value; @@ -846,7 +923,6 @@ public T getObject(String parameterName, Class type) throws SQLException @Override public short getShort(int index) throws SQLServerException { - loggerExternal.entering(getClassNameLogging(), "getShort", index); checkClosed(); Short value = (Short) getValue(index, JDBCType.SMALLINT); @@ -858,7 +934,8 @@ public short getShort(int index) throws SQLServerException { public short getShort(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getShort", parameterName); checkClosed(); - Short value = (Short) getValue(findColumn(parameterName), JDBCType.SMALLINT); + Short value = (Short) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.SMALLINT); loggerExternal.exiting(getClassNameLogging(), "getShort", value); return null != value ? value : 0; } @@ -877,7 +954,8 @@ public Time getTime(int index) throws SQLServerException { public Time getTime(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getTime", parameterName); checkClosed(); - java.sql.Time value = (java.sql.Time) getValue(findColumn(parameterName), JDBCType.TIME); + java.sql.Time value = (java.sql.Time) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.TIME); loggerExternal.exiting(getClassNameLogging(), "getTime", value); return value; } @@ -897,7 +975,8 @@ public Time getTime(String parameterName, Calendar cal) throws SQLServerExceptio if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "getTime", new Object[] {parameterName, cal}); checkClosed(); - java.sql.Time value = (java.sql.Time) getValue(findColumn(parameterName), JDBCType.TIME, cal); + java.sql.Time value = (java.sql.Time) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.TIME, cal); loggerExternal.exiting(getClassNameLogging(), "getTime", value); return value; } @@ -916,7 +995,8 @@ public Timestamp getTimestamp(int index) throws SQLServerException { public Timestamp getTimestamp(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), GET_TIMESTAMP, parameterName); checkClosed(); - java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(parameterName), JDBCType.TIMESTAMP); + java.sql.Timestamp value = (java.sql.Timestamp) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.TIMESTAMP); loggerExternal.exiting(getClassNameLogging(), GET_TIMESTAMP, value); return value; } @@ -932,11 +1012,12 @@ public Timestamp getTimestamp(int index, Calendar cal) throws SQLServerException } @Override - public Timestamp getTimestamp(String name, Calendar cal) throws SQLServerException { + public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) - loggerExternal.entering(getClassNameLogging(), GET_TIMESTAMP, new Object[] {name, cal}); + loggerExternal.entering(getClassNameLogging(), GET_TIMESTAMP, new Object[] {parameterName, cal}); checkClosed(); - java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(name), JDBCType.TIMESTAMP, cal); + java.sql.Timestamp value = (java.sql.Timestamp) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.TIMESTAMP, cal); loggerExternal.exiting(getClassNameLogging(), GET_TIMESTAMP, value); return value; } @@ -963,7 +1044,8 @@ public Timestamp getDateTime(int index) throws SQLServerException { public Timestamp getDateTime(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getDateTime", parameterName); checkClosed(); - java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(parameterName), JDBCType.DATETIME); + java.sql.Timestamp value = (java.sql.Timestamp) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DATETIME); loggerExternal.exiting(getClassNameLogging(), "getDateTime", value); return value; } @@ -979,11 +1061,12 @@ public Timestamp getDateTime(int index, Calendar cal) throws SQLServerException } @Override - public Timestamp getDateTime(String name, Calendar cal) throws SQLServerException { + public Timestamp getDateTime(String parameterName, Calendar cal) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) - loggerExternal.entering(getClassNameLogging(), "getDateTime", new Object[] {name, cal}); + loggerExternal.entering(getClassNameLogging(), "getDateTime", new Object[] {parameterName, cal}); checkClosed(); - java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(name), JDBCType.DATETIME, cal); + java.sql.Timestamp value = (java.sql.Timestamp) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DATETIME, cal); loggerExternal.exiting(getClassNameLogging(), "getDateTime", value); return value; } @@ -1002,7 +1085,8 @@ public Timestamp getSmallDateTime(int index) throws SQLServerException { public Timestamp getSmallDateTime(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getSmallDateTime", parameterName); checkClosed(); - java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(parameterName), JDBCType.SMALLDATETIME); + java.sql.Timestamp value = (java.sql.Timestamp) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.SMALLDATETIME); loggerExternal.exiting(getClassNameLogging(), "getSmallDateTime", value); return value; } @@ -1018,11 +1102,13 @@ public Timestamp getSmallDateTime(int index, Calendar cal) throws SQLServerExcep } @Override - public Timestamp getSmallDateTime(String name, Calendar cal) throws SQLServerException { + public Timestamp getSmallDateTime(String parameterName, Calendar cal) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) - loggerExternal.entering(getClassNameLogging(), "getSmallDateTime", new Object[] {name, cal}); + loggerExternal.entering(getClassNameLogging(), "getSmallDateTime", new Object[] {parameterName, cal}); checkClosed(); - java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(name), JDBCType.SMALLDATETIME, cal); + java.sql.Timestamp value = (java.sql.Timestamp) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.SMALLDATETIME, + cal); loggerExternal.exiting(getClassNameLogging(), "getSmallDateTime", value); return value; } @@ -1053,8 +1139,8 @@ public microsoft.sql.DateTimeOffset getDateTimeOffset(String parameterName) thro throw new SQLServerException(SQLServerException.getErrString("R_notSupported"), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET, null); - microsoft.sql.DateTimeOffset value = (microsoft.sql.DateTimeOffset) getValue(findColumn(parameterName), - JDBCType.DATETIMEOFFSET); + microsoft.sql.DateTimeOffset value = (microsoft.sql.DateTimeOffset) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DATETIMEOFFSET); loggerExternal.exiting(getClassNameLogging(), "getDateTimeOffset", value); return value; } @@ -1084,7 +1170,8 @@ public final java.io.InputStream getAsciiStream(int parameterIndex) throws SQLSe public final java.io.InputStream getAsciiStream(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getAsciiStream", parameterName); checkClosed(); - InputStream value = (InputStream) getStream(findColumn(parameterName), StreamType.ASCII); + InputStream value = (InputStream) getStream( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), StreamType.ASCII); loggerExternal.exiting(getClassNameLogging(), "getAsciiStream", value); return value; } @@ -1102,7 +1189,8 @@ public BigDecimal getBigDecimal(int parameterIndex) throws SQLServerException { public BigDecimal getBigDecimal(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getBigDecimal", parameterName); checkClosed(); - BigDecimal value = (BigDecimal) getValue(findColumn(parameterName), JDBCType.DECIMAL); + BigDecimal value = (BigDecimal) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.DECIMAL); loggerExternal.exiting(getClassNameLogging(), "getBigDecimal", value); return value; } @@ -1120,7 +1208,8 @@ public BigDecimal getMoney(int parameterIndex) throws SQLServerException { public BigDecimal getMoney(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getMoney", parameterName); checkClosed(); - BigDecimal value = (BigDecimal) getValue(findColumn(parameterName), JDBCType.MONEY); + BigDecimal value = (BigDecimal) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.MONEY); loggerExternal.exiting(getClassNameLogging(), "getMoney", value); return value; } @@ -1138,7 +1227,8 @@ public BigDecimal getSmallMoney(int parameterIndex) throws SQLServerException { public BigDecimal getSmallMoney(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getSmallMoney", parameterName); checkClosed(); - BigDecimal value = (BigDecimal) getValue(findColumn(parameterName), JDBCType.SMALLMONEY); + BigDecimal value = (BigDecimal) getValue( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), JDBCType.SMALLMONEY); loggerExternal.exiting(getClassNameLogging(), "getSmallMoney", value); return value; } @@ -1156,7 +1246,8 @@ public final java.io.InputStream getBinaryStream(int parameterIndex) throws SQLS public final java.io.InputStream getBinaryStream(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getBinaryStream", parameterName); checkClosed(); - InputStream value = (InputStream) getStream(findColumn(parameterName), StreamType.BINARY); + InputStream value = (InputStream) getStream( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), StreamType.BINARY); loggerExternal.exiting(getClassNameLogging(), "getBinaryStream", value); return value; } @@ -1174,7 +1265,8 @@ public Blob getBlob(int parameterIndex) throws SQLServerException { public Blob getBlob(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getBlob", parameterName); checkClosed(); - Blob value = (Blob) getValue(findColumn(parameterName), JDBCType.BLOB); + Blob value = (Blob) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.BLOB); loggerExternal.exiting(getClassNameLogging(), "getBlob", value); return value; } @@ -1192,7 +1284,8 @@ public final java.io.Reader getCharacterStream(int parameterIndex) throws SQLSer public final java.io.Reader getCharacterStream(String parameterName) throws SQLException { loggerExternal.entering(getClassNameLogging(), "getCharacterStream", parameterName); checkClosed(); - Reader reader = (Reader) getStream(findColumn(parameterName), StreamType.CHARACTER); + Reader reader = (Reader) getStream( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), StreamType.CHARACTER); loggerExternal.exiting(getClassNameLogging(), "getCharacterSream", reader); return reader; } @@ -1210,7 +1303,8 @@ public final java.io.Reader getNCharacterStream(int parameterIndex) throws SQLEx public final java.io.Reader getNCharacterStream(String parameterName) throws SQLException { loggerExternal.entering(getClassNameLogging(), "getNCharacterStream", parameterName); checkClosed(); - Reader reader = (Reader) getStream(findColumn(parameterName), StreamType.NCHARACTER); + Reader reader = (Reader) getStream( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), StreamType.NCHARACTER); loggerExternal.exiting(getClassNameLogging(), "getNCharacterStream", reader); return reader; } @@ -1240,7 +1334,8 @@ public Clob getClob(int parameterIndex) throws SQLServerException { public Clob getClob(String parameterName) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getClob", parameterName); checkClosed(); - Clob clob = (Clob) getValue(findColumn(parameterName), JDBCType.CLOB); + Clob clob = (Clob) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.CLOB); loggerExternal.exiting(getClassNameLogging(), "getClob", clob); return clob; } @@ -1258,7 +1353,8 @@ public NClob getNClob(int parameterIndex) throws SQLException { public NClob getNClob(String parameterName) throws SQLException { loggerExternal.entering(getClassNameLogging(), "getNClob", parameterName); checkClosed(); - NClob nClob = (NClob) getValue(findColumn(parameterName), JDBCType.NCLOB); + NClob nClob = (NClob) getValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), + JDBCType.NCLOB); loggerExternal.exiting(getClassNameLogging(), "getNClob", nClob); return nClob; } @@ -1272,7 +1368,7 @@ public Object getObject(int parameterIndex, java.util.Map> map) @Override public Object getObject(String parameterName, java.util.Map> m) throws SQLException { checkClosed(); - return getObject(findColumn(parameterName), m); + return getObject(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD), m); } @Override @@ -1284,7 +1380,7 @@ public Ref getRef(int parameterIndex) throws SQLException { @Override public Ref getRef(String parameterName) throws SQLException { checkClosed(); - return getRef(findColumn(parameterName)); + return getRef(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD)); } @Override @@ -1296,106 +1392,153 @@ public java.sql.Array getArray(int parameterIndex) throws SQLException { @Override public java.sql.Array getArray(String parameterName) throws SQLException { checkClosed(); - return getArray(findColumn(parameterName)); + return getArray(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD)); } - /* JDBC 3.0 */ + /** + * handle `@name` as well as `name`, since `@name` is what's returned by DatabaseMetaData#getProcedureColumns + * + * @param columnName + * @return + */ + private String stripLeadingAtSign(String columnName) { + return (columnName.startsWith("@") ? columnName.substring(1, columnName.length()) : columnName); + } + /* JDBC 3.0 */ /** * Find a column's index given its name. - * + * * @param columnName * the name * @throws SQLServerException * when an error occurs * @return the index */ - private int findColumn(String columnName) throws SQLServerException { - if (null == parameterNames) { - try (SQLServerStatement s = (SQLServerStatement) connection.createStatement()) { - // Note we are concatenating the information from the passed in sql, not any arguments provided by the - // user - // if the user can execute the sql, any fragments of it is potentially executed via the meta data call - // through injection - // is not a security issue. - - ThreePartName threePartName = ThreePartName.parse(procedureName); - StringBuilder metaQuery = new StringBuilder("exec sp_sproc_columns "); - if (null != threePartName.getDatabasePart()) { - metaQuery.append("@procedure_qualifier="); - metaQuery.append(threePartName.getDatabasePart()); - metaQuery.append(", "); - } - if (null != threePartName.getOwnerPart()) { - metaQuery.append("@procedure_owner="); - metaQuery.append(threePartName.getOwnerPart()); - metaQuery.append(", "); - } - if (null != threePartName.getProcedurePart()) { - // we should always have a procedure name part - metaQuery.append("@procedure_name="); - metaQuery.append(threePartName.getProcedurePart()); - metaQuery.append(" , @ODBCVer=3, @fUsePattern=0"); - } else { - // This should rarely happen, this will only happen if we can't find the stored procedure name - // invalidly formatted call syntax. - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_parameterNotDefinedForProcedure")); - Object[] msgArgs = {columnName, ""}; - SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), SQLSTATE_07009, - false); - } + private int findColumn(String columnName, CallableStatementGetterSetterMethod method) throws SQLServerException { + isSetByName = true; + if (!connection.getUseFlexibleCallableStatements() && isSetByName && isSetByIndex) { + SQLServerException.makeFromDriverError(connection, this, + SQLServerException.getErrString("R_noNamedAndIndexedParameters"), null, false); + } + + // If inOutParam is null, likely the statement was closed beforehand. + if (null == inOutParam) { + SQLServerException.makeFromDriverError(connection, this, + SQLServerException.getErrString("R_statementIsClosed"), null, false); + } + + if (connection.getUseFlexibleCallableStatements() || isCursorable(executeMethod)) { + // Stored procedures with cursorable methods are not called directly, so we have to get the metadata + if (parameterNames == null) { + try (SQLServerStatement s = (SQLServerStatement) connection.createStatement()) { + // Note we are concatenating the information from the passed in sql, not any arguments provided by the + // user + // if the user can execute the sql, any fragments of it is potentially executed via the meta data call + // through injection + // is not a security issue. + ThreePartName threePartName = ThreePartName.parse(procedureName); + StringBuilder metaQuery = new StringBuilder("exec sp_sproc_columns "); + if (null != threePartName.getDatabasePart()) { + metaQuery.append("@procedure_qualifier="); + metaQuery.append(threePartName.getDatabasePart()); + metaQuery.append(", "); + } + if (null != threePartName.getOwnerPart()) { + metaQuery.append("@procedure_owner="); + metaQuery.append(threePartName.getOwnerPart()); + metaQuery.append(", "); + } + if (null != threePartName.getProcedurePart()) { + // we should always have a procedure name part + metaQuery.append("@procedure_name="); + metaQuery.append(threePartName.getProcedurePart()); + metaQuery.append(" , @ODBCVer=3, @fUsePattern=0"); + } else { + // This should rarely happen, this will only happen if we can't find the stored procedure name + // invalidly formatted call syntax. + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_parameterNotDefinedForProcedure")); + Object[] msgArgs = {columnName, ""}; + SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), SQLSTATE_07009, + false); + } - try (ResultSet rs = s.executeQueryInternal(metaQuery.toString())) { - parameterNames = new HashMap<>(); - insensitiveParameterNames = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - int columnIndex = 0; - while (rs.next()) { - String p = rs.getString(4).trim(); - parameterNames.put(p, columnIndex); - insensitiveParameterNames.put(p, columnIndex++); + try (ResultSet rs = s.executeQueryInternal(metaQuery.toString())) { + parameterNames = new HashMap<>(); + insensitiveParameterNames = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + int columnIndex = 0; + while (rs.next()) { + String p = rs.getString(4).trim(); + parameterNames.put(p, columnIndex); + insensitiveParameterNames.put(p, columnIndex++); + } } + } catch (SQLException e) { + SQLServerException.makeFromDriverError(connection, this, e.toString(), null, false); } - } catch (SQLException e) { - SQLServerException.makeFromDriverError(connection, this, e.toString(), null, false); } - } + // If the server didn't return anything (eg. the param names for the sp_sproc_columns), user might not + // have required permissions to view all the parameterNames. And, there's also the case depending on the permissions, + // @RETURN_VALUE may or may not be present. So, the parameterNames list might have an additional +1 parameter. + if (null != parameterNames && parameterNames.size() <= 1) { + return map.computeIfAbsent(columnName, ifAbsent -> ai.incrementAndGet()); + } - // If the server didn't return anything (eg. the param names for the sp_sproc_columns), user might not - // have required permissions to view all the parameterNames. And, there's also the case depending on the permissions, - // @RETURN_VALUE may or may not be present. So, the parameterNames list might have an additional +1 parameter. - if (null != parameterNames && parameterNames.size() <= 1) { - return map.computeIfAbsent(columnName, ifAbsent -> ai.incrementAndGet()); - } + // handle `@name` as well as `name`, since `@name` is what's returned + // by DatabaseMetaData#getProcedureColumns + String columnNameWithSign = columnName.startsWith("@") ? columnName : "@" + columnName; + + // In order to be as accurate as possible when locating parameter name + // indexes, as well as be deterministic when running on various client + // locales, we search for parameter names using the following scheme: - // handle `@name` as well as `name`, since `@name` is what's returned - // by DatabaseMetaData#getProcedureColumns - String columnNameWithSign = columnName.startsWith("@") ? columnName : "@" + columnName; + // 1. Search using case-sensitive non-locale specific (binary) compare first. + // 2. Search using case-insensitive, non-locale specific (binary) compare last. + int matchPos = (parameterNames != null && parameterNames.containsKey(columnNameWithSign)) ? parameterNames + .get(columnNameWithSign) : -1; + if (matchPos == -1 && insensitiveParameterNames.containsKey(columnNameWithSign)) { + matchPos = insensitiveParameterNames.get(columnNameWithSign); + } - // In order to be as accurate as possible when locating parameter name - // indexes, as well as be deterministic when running on various client - // locales, we search for parameter names using the following scheme: + if (matchPos == -1) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_parameterNotDefinedForProcedure")); + Object[] msgArgs = {columnName, procedureName}; + SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), SQLSTATE_07009, false); + } - // 1. Search using case-sensitive non-locale specific (binary) compare first. - // 2. Search using case-insensitive, non-locale specific (binary) compare last. - Integer matchPos = (parameterNames != null) ? parameterNames.get(columnNameWithSign) : null; - if (null == matchPos) { - matchPos = insensitiveParameterNames.get(columnNameWithSign); + // @RETURN_VALUE is always in the list. If the user uses return value ?=call(@p1) syntax then + // @p1 is index 2 otherwise its index 1. + if (bReturnValueSyntax) // 3.2717 + return matchPos + 1; + else + return matchPos; } - if (null == matchPos) { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_parameterNotDefinedForProcedure")); - Object[] msgArgs = {columnName, procedureName}; - SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), SQLSTATE_07009, false); + + String columnNameWithoutAtSign = stripLeadingAtSign(columnName); + + for (int i = 0; i < inOutParam.length; i++) { + if (null != inOutParam[i].getName() + && inOutParam[i].getName().equalsIgnoreCase(columnNameWithoutAtSign)) { + return i + 1; + } + } + + if (method == CallableStatementGetterSetterMethod.IS_SETTER_METHOD) { + for (int i = 0; i < inOutParam.length; i++) { + // if it is not already registered as output param or the parameter is not an input parameter, then + // set the param name and return index. + if (null == inOutParam[i].getName() && !inOutParam[i].isReturnValue() + && null == inOutParam[i].getInputDTV() && null == inOutParam[i].getRegisteredOutDTV()) { + inOutParam[i].setName(columnNameWithoutAtSign); + return i + 1; + } + } } - // @RETURN_VALUE is always in the list. If the user uses return value ?=call(@p1) syntax then - // @p1 is index 2 otherwise its index 1. - if (bReturnValueSyntax) // 3.2717 - return matchPos + 1; - else - return matchPos; + return -1; } @Override @@ -1405,7 +1548,8 @@ public void setTimestamp(String parameterName, java.sql.Timestamp value, loggerExternal.entering(getClassNameLogging(), "setTimeStamp", new Object[] {parameterName, value, calendar}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TIMESTAMP, value, JavaType.TIMESTAMP, calendar, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TIMESTAMP, + value, JavaType.TIMESTAMP, calendar, false); loggerExternal.exiting(getClassNameLogging(), "setTimeStamp"); } @@ -1416,7 +1560,8 @@ public void setTimestamp(String parameterName, java.sql.Timestamp value, Calenda loggerExternal.entering(getClassNameLogging(), "setTimeStamp", new Object[] {parameterName, value, calendar, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TIMESTAMP, value, JavaType.TIMESTAMP, calendar, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TIMESTAMP, + value, JavaType.TIMESTAMP, calendar, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setTimeStamp"); } @@ -1425,7 +1570,8 @@ public void setTime(String parameterName, java.sql.Time value, Calendar calendar if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {parameterName, value, calendar}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TIME, value, JavaType.TIME, calendar, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TIME, value, + JavaType.TIME, calendar, false); loggerExternal.exiting(getClassNameLogging(), "setTime"); } @@ -1436,7 +1582,8 @@ public void setTime(String parameterName, java.sql.Time value, Calendar calendar loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {parameterName, value, calendar, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TIME, value, JavaType.TIME, calendar, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TIME, value, + JavaType.TIME, calendar, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setTime"); } @@ -1445,7 +1592,8 @@ public void setDate(String parameterName, java.sql.Date value, Calendar calendar if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDate", new Object[] {parameterName, value, calendar}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DATE, value, JavaType.DATE, calendar, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATE, value, + JavaType.DATE, calendar, false); loggerExternal.exiting(getClassNameLogging(), "setDate"); } @@ -1456,17 +1604,18 @@ public void setDate(String parameterName, java.sql.Date value, Calendar calendar loggerExternal.entering(getClassNameLogging(), "setDate", new Object[] {parameterName, value, calendar, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DATE, value, JavaType.DATE, calendar, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATE, value, + JavaType.DATE, calendar, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setDate"); } @Override - public final void setCharacterStream(String parameterName, Reader reader) throws SQLException { + public final void setCharacterStream(String parameterName, Reader value) throws SQLException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) - loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[] {parameterName, reader}); + loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[] {parameterName, value}); checkClosed(); - setStream(findColumn(parameterName), StreamType.CHARACTER, reader, JavaType.READER, - DataTypes.UNKNOWN_STREAM_LENGTH); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.CHARACTER, + value, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH); loggerExternal.exiting(getClassNameLogging(), "setCharacterStream"); } @@ -1476,18 +1625,19 @@ public final void setCharacterStream(String parameterName, Reader value, int len loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName), StreamType.CHARACTER, value, JavaType.READER, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.CHARACTER, + value, JavaType.READER, length); loggerExternal.exiting(getClassNameLogging(), "setCharacterStream"); } @Override - public final void setCharacterStream(String parameterName, Reader reader, long length) throws SQLException { - + public final void setCharacterStream(String parameterName, Reader value, long length) throws SQLException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setCharacterStream", - new Object[] {parameterName, reader, length}); + new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName), StreamType.CHARACTER, reader, JavaType.READER, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.CHARACTER, + value, JavaType.READER, length); loggerExternal.exiting(getClassNameLogging(), "setCharacterStream"); } @@ -1496,8 +1646,8 @@ public final void setNCharacterStream(String parameterName, Reader value) throws if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNCharacterStream", new Object[] {parameterName, value}); checkClosed(); - setStream(findColumn(parameterName), StreamType.NCHARACTER, value, JavaType.READER, - DataTypes.UNKNOWN_STREAM_LENGTH); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.NCHARACTER, + value, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH); loggerExternal.exiting(getClassNameLogging(), "setNCharacterStream"); } @@ -1507,7 +1657,8 @@ public final void setNCharacterStream(String parameterName, Reader value, long l loggerExternal.entering(getClassNameLogging(), "setNCharacterStream", new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName), StreamType.NCHARACTER, value, JavaType.READER, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.NCHARACTER, + value, JavaType.READER, length); loggerExternal.exiting(getClassNameLogging(), "setNCharacterStream"); } @@ -1516,17 +1667,18 @@ public final void setClob(String parameterName, Clob value) throws SQLException if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setClob", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.CLOB, value, JavaType.CLOB, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.CLOB, value, + JavaType.CLOB, false); loggerExternal.exiting(getClassNameLogging(), "setClob"); } @Override - public final void setClob(String parameterName, Reader reader) throws SQLException { + public final void setClob(String parameterName, Reader value) throws SQLException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) - loggerExternal.entering(getClassNameLogging(), "setClob", new Object[] {parameterName, reader}); + loggerExternal.entering(getClassNameLogging(), "setClob", new Object[] {parameterName, value}); checkClosed(); - setStream(findColumn(parameterName), StreamType.CHARACTER, reader, JavaType.READER, - DataTypes.UNKNOWN_STREAM_LENGTH); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.CHARACTER, + value, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH); loggerExternal.exiting(getClassNameLogging(), "setClob"); } @@ -1535,7 +1687,8 @@ public final void setClob(String parameterName, Reader value, long length) throw if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setClob", new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName), StreamType.CHARACTER, value, JavaType.READER, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.CHARACTER, + value, JavaType.READER, length); loggerExternal.exiting(getClassNameLogging(), "setClob"); } @@ -1544,26 +1697,28 @@ public final void setNClob(String parameterName, NClob value) throws SQLExceptio if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.NCLOB, value, JavaType.NCLOB, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.NCLOB, value, + JavaType.NCLOB, false); loggerExternal.exiting(getClassNameLogging(), "setNClob"); } @Override - public final void setNClob(String parameterName, Reader reader) throws SQLException { + public final void setNClob(String parameterName, Reader value) throws SQLException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) - loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterName, reader}); + loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterName, value}); checkClosed(); - setStream(findColumn(parameterName), StreamType.NCHARACTER, reader, JavaType.READER, - DataTypes.UNKNOWN_STREAM_LENGTH); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.NCHARACTER, + value, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH); loggerExternal.exiting(getClassNameLogging(), "setNClob"); } @Override - public final void setNClob(String parameterName, Reader reader, long length) throws SQLException { + public final void setNClob(String parameterName, Reader value, long length) throws SQLException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) - loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterName, reader, length}); + loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName), StreamType.NCHARACTER, reader, JavaType.READER, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.NCHARACTER, + value, JavaType.READER, length); loggerExternal.exiting(getClassNameLogging(), "setNClob"); } @@ -1572,7 +1727,8 @@ public final void setNString(String parameterName, String value) throws SQLExcep if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNString", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.NVARCHAR, value, JavaType.STRING, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.NVARCHAR, + value, JavaType.STRING, false); loggerExternal.exiting(getClassNameLogging(), "setNString"); } @@ -1582,7 +1738,8 @@ public final void setNString(String parameterName, String value, boolean forceEn loggerExternal.entering(getClassNameLogging(), "setNString", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.NVARCHAR, value, JavaType.STRING, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.NVARCHAR, + value, JavaType.STRING, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setNString"); } @@ -1591,7 +1748,7 @@ public void setObject(String parameterName, Object value) throws SQLServerExcept if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {parameterName, value}); checkClosed(); - setObjectNoType(findColumn(parameterName), value, false); + setObjectNoType(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), value, false); loggerExternal.exiting(getClassNameLogging(), "setObject"); } @@ -1602,12 +1759,15 @@ public void setObject(String parameterName, Object value, int sqlType) throws SQ loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {parameterName, value, sqlType}); checkClosed(); if (microsoft.sql.Types.STRUCTURED == sqlType) { - tvpName = getTVPNameFromObject(findColumn(parameterName), value); - setObject(setterGetParam(findColumn(parameterName)), value, JavaType.TVP, JDBCType.TVP, null, null, false, - findColumn(parameterName), tvpName); + tvpName = getTVPNameFromObject( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), value); + setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), + value, JavaType.TVP, JDBCType.TVP, null, null, false, + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), tvpName); } else - setObject(setterGetParam(findColumn(parameterName)), value, JavaType.of(value), JDBCType.of(sqlType), null, - null, false, findColumn(parameterName), tvpName); + setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), + value, JavaType.of(value), JDBCType.of(sqlType), null, null, false, + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), tvpName); loggerExternal.exiting(getClassNameLogging(), "setObject"); } @@ -1617,8 +1777,9 @@ public void setObject(String parameterName, Object value, int sqlType, int decim loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {parameterName, value, sqlType, decimals}); checkClosed(); - setObject(setterGetParam(findColumn(parameterName)), value, JavaType.of(value), JDBCType.of(sqlType), decimals, - null, false, findColumn(parameterName), null); + setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), value, + JavaType.of(value), JDBCType.of(sqlType), decimals, null, false, + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), null); loggerExternal.exiting(getClassNameLogging(), "setObject"); } @@ -1634,9 +1795,10 @@ public void setObject(String parameterName, Object value, int sqlType, int decim // this is the number of digits after the decimal point. // For all other types, this value will be ignored. - setObject(setterGetParam(findColumn(parameterName)), value, JavaType.of(value), JDBCType.of(sqlType), + setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), value, + JavaType.of(value), JDBCType.of(sqlType), (java.sql.Types.NUMERIC == sqlType || java.sql.Types.DECIMAL == sqlType) ? decimals : null, null, - forceEncrypt, findColumn(parameterName), null); + forceEncrypt, findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), null); loggerExternal.exiting(getClassNameLogging(), "setObject"); } @@ -1654,10 +1816,11 @@ public final void setObject(String parameterName, Object value, int targetSqlTyp // InputStream and Reader, this is the length of the data in the stream or reader. // For all other types, this value will be ignored. - setObject(setterGetParam(findColumn(parameterName)), value, JavaType.of(value), JDBCType.of(targetSqlType), + setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), value, + JavaType.of(value), JDBCType.of(targetSqlType), (java.sql.Types.NUMERIC == targetSqlType || java.sql.Types.DECIMAL == targetSqlType || InputStream.class.isInstance(value) || Reader.class.isInstance(value)) ? scale : null, - precision, false, findColumn(parameterName), null); + precision, false, findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), null); loggerExternal.exiting(getClassNameLogging(), "setObject"); } @@ -1667,8 +1830,8 @@ public final void setAsciiStream(String parameterName, InputStream value) throws if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[] {parameterName, value}); checkClosed(); - setStream(findColumn(parameterName), StreamType.ASCII, value, JavaType.INPUTSTREAM, - DataTypes.UNKNOWN_STREAM_LENGTH); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.ASCII, + value, JavaType.INPUTSTREAM, DataTypes.UNKNOWN_STREAM_LENGTH); loggerExternal.exiting(getClassNameLogging(), "setAsciiStream"); } @@ -1678,7 +1841,8 @@ public final void setAsciiStream(String parameterName, InputStream value, int le loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName), StreamType.ASCII, value, JavaType.INPUTSTREAM, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.ASCII, + value, JavaType.INPUTSTREAM, length); loggerExternal.exiting(getClassNameLogging(), "setAsciiStream"); } @@ -1688,7 +1852,8 @@ public final void setAsciiStream(String parameterName, InputStream value, long l loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName), StreamType.ASCII, value, JavaType.INPUTSTREAM, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.ASCII, + value, JavaType.INPUTSTREAM, length); loggerExternal.exiting(getClassNameLogging(), "setAsciiStream"); } @@ -1697,8 +1862,8 @@ public final void setBinaryStream(String parameterName, InputStream value) throw if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[] {parameterName, value}); checkClosed(); - setStream(findColumn(parameterName), StreamType.BINARY, value, JavaType.INPUTSTREAM, - DataTypes.UNKNOWN_STREAM_LENGTH); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.BINARY, + value, JavaType.INPUTSTREAM, DataTypes.UNKNOWN_STREAM_LENGTH); loggerExternal.exiting(getClassNameLogging(), "setBinaryStream"); } @@ -1708,7 +1873,8 @@ public final void setBinaryStream(String parameterName, InputStream value, int l loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName), StreamType.BINARY, value, JavaType.INPUTSTREAM, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.BINARY, + value, JavaType.INPUTSTREAM, length); loggerExternal.exiting(getClassNameLogging(), "setBinaryStream"); } @@ -1718,7 +1884,8 @@ public final void setBinaryStream(String parameterName, InputStream value, long loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[] {parameterName, value, length}); checkClosed(); - setStream(findColumn(parameterName), StreamType.BINARY, value, JavaType.INPUTSTREAM, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.BINARY, + value, JavaType.INPUTSTREAM, length); loggerExternal.exiting(getClassNameLogging(), "setBinaryStream"); } @@ -1727,7 +1894,8 @@ public final void setBlob(String parameterName, Blob inputStream) throws SQLExce if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[] {parameterName, inputStream}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.BLOB, inputStream, JavaType.BLOB, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BLOB, + inputStream, JavaType.BLOB, false); loggerExternal.exiting(getClassNameLogging(), "setBlob"); } @@ -1736,8 +1904,8 @@ public final void setBlob(String parameterName, InputStream value) throws SQLExc if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[] {parameterName, value}); checkClosed(); - setStream(findColumn(parameterName), StreamType.BINARY, value, JavaType.INPUTSTREAM, - DataTypes.UNKNOWN_STREAM_LENGTH); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.BINARY, + value, JavaType.INPUTSTREAM, DataTypes.UNKNOWN_STREAM_LENGTH); loggerExternal.exiting(getClassNameLogging(), "setBlob"); } @@ -1747,7 +1915,8 @@ public final void setBlob(String parameterName, InputStream inputStream, long le loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[] {parameterName, inputStream, length}); checkClosed(); - setStream(findColumn(parameterName), StreamType.BINARY, inputStream, JavaType.INPUTSTREAM, length); + setStream(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), StreamType.BINARY, + inputStream, JavaType.INPUTSTREAM, length); loggerExternal.exiting(getClassNameLogging(), "setBlob"); } @@ -1756,7 +1925,8 @@ public void setTimestamp(String parameterName, java.sql.Timestamp value) throws if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TIMESTAMP, value, JavaType.TIMESTAMP, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TIMESTAMP, + value, JavaType.TIMESTAMP, false); loggerExternal.exiting(getClassNameLogging(), "setTimestamp"); } @@ -1765,7 +1935,8 @@ public void setTimestamp(String parameterName, java.sql.Timestamp value, int sca if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TIMESTAMP, value, JavaType.TIMESTAMP, null, scale, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TIMESTAMP, + value, JavaType.TIMESTAMP, null, scale, false); loggerExternal.exiting(getClassNameLogging(), "setTimestamp"); } @@ -1776,7 +1947,8 @@ public void setTimestamp(String parameterName, java.sql.Timestamp value, int sca loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TIMESTAMP, value, JavaType.TIMESTAMP, null, scale, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TIMESTAMP, + value, JavaType.TIMESTAMP, null, scale, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setTimestamp"); } @@ -1785,7 +1957,8 @@ public void setDateTimeOffset(String parameterName, microsoft.sql.DateTimeOffset if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DATETIMEOFFSET, value, JavaType.DATETIMEOFFSET, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATETIMEOFFSET, + value, JavaType.DATETIMEOFFSET, false); loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset"); } @@ -1795,8 +1968,8 @@ public void setDateTimeOffset(String parameterName, microsoft.sql.DateTimeOffset if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DATETIMEOFFSET, value, JavaType.DATETIMEOFFSET, null, scale, - false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATETIMEOFFSET, + value, JavaType.DATETIMEOFFSET, null, scale, false); loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset"); } @@ -1807,8 +1980,8 @@ public void setDateTimeOffset(String parameterName, microsoft.sql.DateTimeOffset loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DATETIMEOFFSET, value, JavaType.DATETIMEOFFSET, null, scale, - forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATETIMEOFFSET, + value, JavaType.DATETIMEOFFSET, null, scale, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset"); } @@ -1817,7 +1990,8 @@ public void setDate(String parameterName, java.sql.Date value) throws SQLServerE if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDate", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DATE, value, JavaType.DATE, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATE, value, + JavaType.DATE, false); loggerExternal.exiting(getClassNameLogging(), "setDate"); } @@ -1826,7 +2000,8 @@ public void setTime(String parameterName, java.sql.Time value) throws SQLServerE if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TIME, value, JavaType.TIME, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TIME, value, + JavaType.TIME, false); loggerExternal.exiting(getClassNameLogging(), "setTime"); } @@ -1835,7 +2010,8 @@ public void setTime(String parameterName, java.sql.Time value, int scale) throws if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TIME, value, JavaType.TIME, null, scale, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TIME, value, + JavaType.TIME, null, scale, false); loggerExternal.exiting(getClassNameLogging(), "setTime"); } @@ -1846,7 +2022,8 @@ public void setTime(String parameterName, java.sql.Time value, int scale, loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TIME, value, JavaType.TIME, null, scale, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TIME, value, + JavaType.TIME, null, scale, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setTime"); } @@ -1855,7 +2032,8 @@ public void setDateTime(String parameterName, java.sql.Timestamp value) throws S if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDateTime", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DATETIME, value, JavaType.TIMESTAMP, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATETIME, + value, JavaType.TIMESTAMP, false); loggerExternal.exiting(getClassNameLogging(), "setDateTime"); } @@ -1866,7 +2044,8 @@ public void setDateTime(String parameterName, java.sql.Timestamp value, loggerExternal.entering(getClassNameLogging(), "setDateTime", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DATETIME, value, JavaType.TIMESTAMP, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DATETIME, + value, JavaType.TIMESTAMP, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setDateTime"); } @@ -1875,7 +2054,8 @@ public void setSmallDateTime(String parameterName, java.sql.Timestamp value) thr if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setSmallDateTime", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.SMALLDATETIME, value, JavaType.TIMESTAMP, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.SMALLDATETIME, + value, JavaType.TIMESTAMP, false); loggerExternal.exiting(getClassNameLogging(), "setSmallDateTime"); } @@ -1886,7 +2066,8 @@ public void setSmallDateTime(String parameterName, java.sql.Timestamp value, loggerExternal.entering(getClassNameLogging(), "setSmallDateTime", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.SMALLDATETIME, value, JavaType.TIMESTAMP, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.SMALLDATETIME, + value, JavaType.TIMESTAMP, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setSmallDateTime"); } @@ -1895,7 +2076,8 @@ public void setUniqueIdentifier(String parameterName, String guid) throws SQLSer if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setUniqueIdentifier", new Object[] {parameterName, guid}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.GUID, guid, JavaType.STRING, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.GUID, guid, + JavaType.STRING, false); loggerExternal.exiting(getClassNameLogging(), "setUniqueIdentifier"); } @@ -1905,7 +2087,8 @@ public void setUniqueIdentifier(String parameterName, String guid, boolean force loggerExternal.entering(getClassNameLogging(), "setUniqueIdentifier", new Object[] {parameterName, guid, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.GUID, guid, JavaType.STRING, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.GUID, guid, + JavaType.STRING, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setUniqueIdentifier"); } @@ -1914,7 +2097,8 @@ public void setBytes(String parameterName, byte[] value) throws SQLServerExcepti if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBytes", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.BINARY, value, JavaType.BYTEARRAY, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BINARY, value, + JavaType.BYTEARRAY, false); loggerExternal.exiting(getClassNameLogging(), "setBytes"); } @@ -1924,7 +2108,8 @@ public void setBytes(String parameterName, byte[] value, boolean forceEncrypt) t loggerExternal.entering(getClassNameLogging(), "setBytes", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.BINARY, value, JavaType.BYTEARRAY, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BINARY, value, + JavaType.BYTEARRAY, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setBytes"); } @@ -1933,7 +2118,8 @@ public void setByte(String parameterName, byte value) throws SQLServerException if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setByte", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TINYINT, value, JavaType.BYTE, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TINYINT, value, + JavaType.BYTE, false); loggerExternal.exiting(getClassNameLogging(), "setByte"); } @@ -1943,7 +2129,8 @@ public void setByte(String parameterName, byte value, boolean forceEncrypt) thro loggerExternal.entering(getClassNameLogging(), "setByte", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TINYINT, value, JavaType.BYTE, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TINYINT, value, + JavaType.BYTE, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setByte"); } @@ -1952,7 +2139,8 @@ public void setString(String parameterName, String value) throws SQLServerExcept if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setString", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.VARCHAR, value, JavaType.STRING, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.VARCHAR, value, + JavaType.STRING, false); loggerExternal.exiting(getClassNameLogging(), "setString"); } @@ -1962,7 +2150,8 @@ public void setString(String parameterName, String value, boolean forceEncrypt) loggerExternal.entering(getClassNameLogging(), "setString", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.VARCHAR, value, JavaType.STRING, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.VARCHAR, value, + JavaType.STRING, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setString"); } @@ -1971,7 +2160,8 @@ public void setMoney(String parameterName, BigDecimal value) throws SQLServerExc if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setMoney", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.MONEY, value, JavaType.BIGDECIMAL, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.MONEY, value, + JavaType.BIGDECIMAL, false); loggerExternal.exiting(getClassNameLogging(), "setMoney"); } @@ -1981,7 +2171,8 @@ public void setMoney(String parameterName, BigDecimal value, boolean forceEncryp loggerExternal.entering(getClassNameLogging(), "setMoney", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.MONEY, value, JavaType.BIGDECIMAL, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.MONEY, value, + JavaType.BIGDECIMAL, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setMoney"); } @@ -1990,7 +2181,8 @@ public void setSmallMoney(String parameterName, BigDecimal value) throws SQLServ if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setSmallMoney", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.SMALLMONEY, value, JavaType.BIGDECIMAL, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.SMALLMONEY, + value, JavaType.BIGDECIMAL, false); loggerExternal.exiting(getClassNameLogging(), "setSmallMoney"); } @@ -2000,7 +2192,8 @@ public void setSmallMoney(String parameterName, BigDecimal value, boolean forceE loggerExternal.entering(getClassNameLogging(), "setSmallMoney", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.SMALLMONEY, value, JavaType.BIGDECIMAL, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.SMALLMONEY, + value, JavaType.BIGDECIMAL, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setSmallMoney"); } @@ -2009,7 +2202,8 @@ public void setBigDecimal(String parameterName, BigDecimal value) throws SQLServ if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DECIMAL, value, JavaType.BIGDECIMAL, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DECIMAL, value, + JavaType.BIGDECIMAL, false); loggerExternal.exiting(getClassNameLogging(), "setBigDecimal"); } @@ -2020,7 +2214,8 @@ public void setBigDecimal(String parameterName, BigDecimal value, int precision, loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {parameterName, value, precision, scale}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DECIMAL, value, JavaType.BIGDECIMAL, precision, scale, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DECIMAL, value, + JavaType.BIGDECIMAL, precision, scale, false); loggerExternal.exiting(getClassNameLogging(), "setBigDecimal"); } @@ -2031,8 +2226,8 @@ public void setBigDecimal(String parameterName, BigDecimal value, int precision, loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {parameterName, value, precision, scale, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DECIMAL, value, JavaType.BIGDECIMAL, precision, scale, - forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DECIMAL, value, + JavaType.BIGDECIMAL, precision, scale, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setBigDecimal"); } @@ -2041,7 +2236,8 @@ public void setDouble(String parameterName, double value) throws SQLServerExcept if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDouble", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DOUBLE, value, JavaType.DOUBLE, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DOUBLE, value, + JavaType.DOUBLE, false); loggerExternal.exiting(getClassNameLogging(), "setDouble"); } @@ -2051,7 +2247,8 @@ public void setDouble(String parameterName, double value, boolean forceEncrypt) loggerExternal.entering(getClassNameLogging(), "setDouble", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.DOUBLE, value, JavaType.DOUBLE, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.DOUBLE, value, + JavaType.DOUBLE, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setDouble"); } @@ -2060,7 +2257,8 @@ public void setFloat(String parameterName, float value) throws SQLServerExceptio if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setFloat", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.REAL, value, JavaType.FLOAT, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.REAL, value, + JavaType.FLOAT, false); loggerExternal.exiting(getClassNameLogging(), "setFloat"); } @@ -2070,7 +2268,8 @@ public void setFloat(String parameterName, float value, boolean forceEncrypt) th loggerExternal.entering(getClassNameLogging(), "setFloat", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.REAL, value, JavaType.FLOAT, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.REAL, value, + JavaType.FLOAT, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setFloat"); } @@ -2079,7 +2278,8 @@ public void setInt(String parameterName, int value) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setInt", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.INTEGER, value, JavaType.INTEGER, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.INTEGER, value, + JavaType.INTEGER, false); loggerExternal.exiting(getClassNameLogging(), "setInt"); } @@ -2088,7 +2288,8 @@ public void setInt(String parameterName, int value, boolean forceEncrypt) throws if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setInt", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.INTEGER, value, JavaType.INTEGER, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.INTEGER, value, + JavaType.INTEGER, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setInt"); } @@ -2097,7 +2298,8 @@ public void setLong(String parameterName, long value) throws SQLServerException if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setLong", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.BIGINT, value, JavaType.LONG, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BIGINT, value, + JavaType.LONG, false); loggerExternal.exiting(getClassNameLogging(), "setLong"); } @@ -2107,7 +2309,8 @@ public void setLong(String parameterName, long value, boolean forceEncrypt) thro loggerExternal.entering(getClassNameLogging(), "setLong", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.BIGINT, value, JavaType.LONG, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BIGINT, value, + JavaType.LONG, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setLong"); } @@ -2116,7 +2319,8 @@ public void setShort(String parameterName, short value) throws SQLServerExceptio if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setShort", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.SMALLINT, value, JavaType.SHORT, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.SMALLINT, + value, JavaType.SHORT, false); loggerExternal.exiting(getClassNameLogging(), "setShort"); } @@ -2126,7 +2330,8 @@ public void setShort(String parameterName, short value, boolean forceEncrypt) th loggerExternal.entering(getClassNameLogging(), "setShort", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.SMALLINT, value, JavaType.SHORT, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.SMALLINT, + value, JavaType.SHORT, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setShort"); } @@ -2135,7 +2340,8 @@ public void setBoolean(String parameterName, boolean value) throws SQLServerExce if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBoolean", new Object[] {parameterName, value}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.BIT, value, JavaType.BOOLEAN, false); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BIT, value, + JavaType.BOOLEAN, false); loggerExternal.exiting(getClassNameLogging(), "setBoolean"); } @@ -2145,7 +2351,8 @@ public void setBoolean(String parameterName, boolean value, boolean forceEncrypt loggerExternal.entering(getClassNameLogging(), "setBoolean", new Object[] {parameterName, value, forceEncrypt}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.BIT, value, JavaType.BOOLEAN, forceEncrypt); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.BIT, value, + JavaType.BOOLEAN, forceEncrypt); loggerExternal.exiting(getClassNameLogging(), "setBoolean"); } @@ -2154,8 +2361,9 @@ public void setNull(String parameterName, int nType) throws SQLServerException { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNull", new Object[] {parameterName, nType}); checkClosed(); - setObject(setterGetParam(findColumn(parameterName)), null, JavaType.OBJECT, JDBCType.of(nType), null, null, - false, findColumn(parameterName), null); + setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), null, + JavaType.OBJECT, JDBCType.of(nType), null, null, false, + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), null); loggerExternal.exiting(getClassNameLogging(), "setNull"); } @@ -2164,8 +2372,9 @@ public void setNull(String parameterName, int nType, String sTypeName) throws SQ if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNull", new Object[] {parameterName, nType, sTypeName}); checkClosed(); - setObject(setterGetParam(findColumn(parameterName)), null, JavaType.OBJECT, JDBCType.of(nType), null, null, - false, findColumn(parameterName), sTypeName); + setObject(setterGetParam(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD)), null, + JavaType.OBJECT, JDBCType.of(nType), null, null, false, + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), sTypeName); loggerExternal.exiting(getClassNameLogging(), "setNull"); } @@ -2173,43 +2382,49 @@ public void setNull(String parameterName, int nType, String sTypeName) throws SQ public void setURL(String parameterName, URL url) throws SQLException { loggerExternal.entering(getClassNameLogging(), "setURL", parameterName); checkClosed(); - setURL(findColumn(parameterName), url); + setURL(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), url); loggerExternal.exiting(getClassNameLogging(), "setURL"); } @Override public final void setStructured(String parameterName, String tvpName, SQLServerDataTable tvpDataTable) throws SQLServerException { - tvpName = getTVPNameIfNull(findColumn(parameterName), tvpName); + tvpName = getTVPNameIfNull(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + tvpName); if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[] {parameterName, tvpName, tvpDataTable}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TVP, tvpDataTable, JavaType.TVP, tvpName); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TVP, + tvpDataTable, JavaType.TVP, tvpName); loggerExternal.exiting(getClassNameLogging(), "setStructured"); } @Override public final void setStructured(String parameterName, String tvpName, ResultSet tvpResultSet) throws SQLServerException { - tvpName = getTVPNameIfNull(findColumn(parameterName), tvpName); + tvpName = getTVPNameIfNull(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + tvpName); if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[] {parameterName, tvpName, tvpResultSet}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TVP, tvpResultSet, JavaType.TVP, tvpName); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TVP, + tvpResultSet, JavaType.TVP, tvpName); loggerExternal.exiting(getClassNameLogging(), "setStructured"); } @Override public final void setStructured(String parameterName, String tvpName, ISQLServerDataRecord tvpDataRecord) throws SQLServerException { - tvpName = getTVPNameIfNull(findColumn(parameterName), tvpName); + tvpName = getTVPNameIfNull(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), + tvpName); if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[] {parameterName, tvpName, tvpDataRecord}); checkClosed(); - setValue(findColumn(parameterName), JDBCType.TVP, tvpDataRecord, JavaType.TVP, tvpName); + setValue(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), JDBCType.TVP, + tvpDataRecord, JavaType.TVP, tvpName); loggerExternal.exiting(getClassNameLogging(), "setStructured"); } @@ -2230,7 +2445,7 @@ public final void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLEx if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setSQLXML", new Object[] {parameterName, xmlObject}); checkClosed(); - setSQLXMLInternal(findColumn(parameterName), xmlObject); + setSQLXMLInternal(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), xmlObject); loggerExternal.exiting(getClassNameLogging(), "setSQLXML"); } @@ -2247,7 +2462,8 @@ public final SQLXML getSQLXML(int parameterIndex) throws SQLException { public final SQLXML getSQLXML(String parameterName) throws SQLException { loggerExternal.entering(getClassNameLogging(), "getSQLXML", parameterName); checkClosed(); - SQLServerSQLXML value = (SQLServerSQLXML) getSQLXMLInternal(findColumn(parameterName)); + SQLServerSQLXML value = (SQLServerSQLXML) getSQLXMLInternal( + findColumn(parameterName, CallableStatementGetterSetterMethod.IS_GETTER_METHOD)); loggerExternal.exiting(getClassNameLogging(), "getSQLXML", value); return value; } @@ -2275,7 +2491,8 @@ public void registerOutParameter(String parameterName, int sqlType, String typeN loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {parameterName, sqlType, typeName}); checkClosed(); - registerOutParameter(findColumn(parameterName), sqlType, typeName); + registerOutParameter(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), sqlType, + typeName); loggerExternal.exiting(getClassNameLogging(), "registerOutParameter"); } @@ -2285,7 +2502,8 @@ public void registerOutParameter(String parameterName, int sqlType, int scale) t loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {parameterName, sqlType, scale}); checkClosed(); - registerOutParameter(findColumn(parameterName), sqlType, scale); + registerOutParameter(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), sqlType, + scale); loggerExternal.exiting(getClassNameLogging(), "registerOutParameter"); } @@ -2296,7 +2514,8 @@ public void registerOutParameter(String parameterName, int sqlType, int precisio loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {parameterName, sqlType, scale}); checkClosed(); - registerOutParameter(findColumn(parameterName), sqlType, precision, scale); + registerOutParameter(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), sqlType, + precision, scale); loggerExternal.exiting(getClassNameLogging(), "registerOutParameter"); } @@ -2306,7 +2525,7 @@ public void registerOutParameter(String parameterName, int sqlType) throws SQLSe loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {parameterName, sqlType}); checkClosed(); - registerOutParameter(findColumn(parameterName), sqlType); + registerOutParameter(findColumn(parameterName, CallableStatementGetterSetterMethod.IS_SETTER_METHOD), sqlType); loggerExternal.exiting(getClassNameLogging(), "registerOutParameter"); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 4ffc8f329..5e148f313 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -164,6 +164,9 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial /** Flag that determines whether the accessToken callback was set **/ private transient SQLServerAccessTokenCallback accessTokenCallback = null; + /** Flag indicating whether to use sp_sproc_columns for parameter name lookup */ + private boolean useFlexibleCallableStatements = SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.getDefaultValue(); + /** * Keep this distinct from _federatedAuthenticationRequested, since some fedauth library types may not need more * info @@ -508,6 +511,10 @@ static ParsedSQLCacheItem parseAndCacheSQL(CityHash128Key key, String sql) throw return cacheItem; } + static int countParams(String sql) { + return locateParams(sql).length; + } + /** Default size for prepared statement caches */ static final int DEFAULT_STATEMENT_POOLING_CACHE_SIZE = 0; @@ -2254,6 +2261,15 @@ Connection connectInternal(Properties propsIn, } trustServerCertificate = isBooleanPropertyOn(sPropKey, sPropValue); + sPropKey = SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.toString(); + sPropValue = activeConnectionProperties.getProperty(sPropKey); + if (null == sPropValue) { + sPropValue = Boolean + .toString(SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.getDefaultValue()); + activeConnectionProperties.setProperty(sPropKey, sPropValue); + } + useFlexibleCallableStatements = isBooleanPropertyOn(sPropKey, sPropValue); + // Set requestedEncryptionLevel according to the value of the encrypt connection property if (encryptOption.compareToIgnoreCase(EncryptOption.FALSE.toString()) == 0) { requestedEncryptionLevel = TDS.ENCRYPT_OFF; @@ -7448,7 +7464,7 @@ String replaceParameterMarkers(String sqlSrc, int[] paramPositions, Parameter[] int paramIndex = 0; while (true) { - int srcEnd = (paramIndex >= paramPositions.length) ? sqlSrc.length() : paramPositions[paramIndex]; + int srcEnd = ParameterUtils.scanSQLForChar('?', sqlSrc, srcBegin); sqlSrc.getChars(srcBegin, srcEnd, sqlDst, dstBegin); dstBegin += srcEnd - srcBegin; @@ -7866,6 +7882,29 @@ public void setAccessTokenCallbackClass(String accessTokenCallbackClass) { this.accessTokenCallbackClass = accessTokenCallbackClass; } + /** + * Returns whether or not sp_sproc_columns is being used for parameter name lookup. + * + * @return useFlexibleCallableStatements + */ + public boolean getUseFlexibleCallableStatements() { + return this.useFlexibleCallableStatements; + } + + /** + * Sets whether or not sp_sproc_columns will be used for parameter name lookup. + * + * @param useFlexibleCallableStatements + * When set to false, sp_sproc_columns is not used for parameter name lookup + * in callable statements. This eliminates a round trip to the server but imposes limitations + * on how parameters are set. When set to false, applications must either reference + * parameters by name or by index, not both. Parameters must also be set in the same + * order as the stored procedure definition. + */ + public void setUseFlexibleCallableStatements(boolean useFlexibleCallableStatements) { + this.useFlexibleCallableStatements = useFlexibleCallableStatements; + } + /** * Cleans up discarded prepared statement handles on the server using batched un-prepare actions if the batching * threshold has been reached. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index 626dca789..a24e5f514 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -250,6 +250,17 @@ public boolean getUseDefaultGSSCredential() { SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue()); } + @Override + public void setUseFlexibleCallableStatements(boolean enable) { + setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.toString(), enable); + } + + @Override + public boolean getUseFlexibleCallableStatements() { + return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.toString(), + SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.getDefaultValue()); + } + @Override public void setAccessToken(String accessToken) { setStringProperty(connectionProps, SQLServerDriverStringProperty.ACCESS_TOKEN.toString(), accessToken); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 3eac09e9f..727e165b1 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -697,6 +697,7 @@ enum SQLServerDriverBooleanProperty { IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION("ignoreOffsetOnDateTimeOffsetConversion", false), USE_DEFAULT_JAAS_CONFIG("useDefaultJaasConfig", false), USE_DEFAULT_GSS_CREDENTIAL("useDefaultGSSCredential", false), + USE_FLEXIBLE_CALLABLE_STATEMENTS("useFlexibleCallableStatements", true), CALC_BIG_DECIMAL_SCALE("calcBigDecimalScale", false); private final String name; @@ -774,6 +775,9 @@ public final class SQLServerDriver implements java.sql.Driver { new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue()), false, TRUE_FALSE), + new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.toString(), + Boolean.toString(SQLServerDriverBooleanProperty.USE_FLEXIBLE_CALLABLE_STATEMENTS.getDefaultValue()), false, + TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.toString(), SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.getDefaultValue(), false, new String[] {KeyStoreAuthentication.JAVA_KEYSTORE_PASSWORD.toString()}), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index cfe578127..52e1d5424 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -374,6 +374,10 @@ final void initParams(int nParams) { for (int i = 0; i < nParams; i++) { inOutParam[i] = new Parameter(Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, connection)); } + + if (bReturnValueSyntax) { + inOutParam[0].setReturnValue(true); + } } @Override @@ -427,13 +431,14 @@ private String buildParamTypeDefinitions(Parameter[] params, boolean renewDefini return ""; // Output looks like @P0 timestamp, @P1 varchar - int stringLen = nCols * 2; // @P - stringLen += nCols; // spaces - stringLen += nCols -1; // commas - if (nCols > 10) - stringLen += 10 + ((nCols - 10) * 2); // @P{0-99} Numbers after p - else - stringLen += nCols; // @P{0-9} Numbers after p less than 10 + int stringLen = nCols * 2; // @P + stringLen += nCols; // spaces + stringLen += nCols - 1; // commas + if (nCols > 10) { + stringLen += 10 + ((nCols - 10) * 2); // @P{0-99} Numbers after p + } else { + stringLen += nCols; // @P{0-9} Numbers after p less than 10 + } // Computing the type definitions up front, so we can get exact string lengths needed for the string builder. String[] typeDefinitions = new String[nCols]; @@ -745,16 +750,23 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException { /** * Sends the statement parameters by RPC. */ - void sendParamsByRPC(TDSWriter tdsWriter, Parameter[] params) throws SQLServerException { + void sendParamsByRPC(TDSWriter tdsWriter, Parameter[] params, boolean bReturnValueSyntax, + boolean callRpcDirectly) throws SQLServerException { char[] cParamName; - for (int index = 0; index < params.length; index++) { + int index = 0; + if (bReturnValueSyntax && !isCursorable(executeMethod) && !isTVPType && callRpcDirectly) { + returnParam = params[index]; + params[index].setReturnValue(true); + index++; + } + for (; index < params.length; index++) { if (JDBCType.TVP == params[index].getJdbcType()) { cParamName = new char[10]; int paramNameLen = SQLServerConnection.makeParamName(index, cParamName, 0, false); tdsWriter.writeByte((byte) paramNameLen); tdsWriter.writeString(new String(cParamName, 0, paramNameLen)); } - params[index].sendByRPC(tdsWriter, this); + params[index].sendByRPC(tdsWriter, callRpcDirectly, this); } } @@ -802,6 +814,25 @@ private void buildServerCursorPrepExecParams(TDSWriter tdsWriter) throws SQLServ tdsWriter.writeRPCInt(null, 0, true); } + private void buildRPCExecParams(TDSWriter tdsWriter) throws SQLServerException { + if (getStatementLogger().isLoggable(java.util.logging.Level.FINE)) { + getStatementLogger().fine(toString() + ": calling PROC" + ", SQL:" + preparedSQL); + } + + expectPrepStmtHandle = false; + executedSqlDirectly = true; + expectCursorOutParams = false; + outParamIndexAdjustment = 0; + tdsWriter.writeShort((short) procedureName.length()); // procedure name length + tdsWriter.writeString(procedureName); + if (connection.isAEv2()) { + tdsWriter.sendEnclavePackage(preparedSQL, enclaveCEKs); + } + + tdsWriter.writeByte((byte) 0); // RPC procedure option 1 + tdsWriter.writeByte((byte) 0); // RPC procedure option 2 + } + private void buildPrepParams(TDSWriter tdsWriter) throws SQLServerException { if (getStatementLogger().isLoggable(java.util.logging.Level.FINE)) getStatementLogger().fine(toString() + ": calling sp_prepare: PreparedHandle:" @@ -1119,6 +1150,7 @@ private boolean doPrepExec(TDSWriter tdsWriter, Parameter[] params, boolean hasN boolean needsPrepare = (hasNewTypeDefinitions && hasExistingTypeDefinitions) || !hasPreparedStatementHandle(); boolean isPrepareMethodSpPrepExec = connection.getPrepareMethod().equals(PrepareMethod.PREPEXEC.toString()); + boolean callRpcDirectly = callRPCDirectly(params); // Cursors don't use statement pooling. if (isCursorable(executeMethod)) { @@ -1127,9 +1159,14 @@ private boolean doPrepExec(TDSWriter tdsWriter, Parameter[] params, boolean hasN else buildServerCursorExecParams(tdsWriter); } else { + // if it is a parameterized stored procedure call and is not TVP, use sp_execute directly. + if (needsPrepare && callRpcDirectly) { + buildRPCExecParams(tdsWriter); + } // Move overhead of needing to do prepare & unprepare to only use cases that need more than one execution. // First execution, use sp_executesql, optimizing for assumption we will not re-use statement. - if (needsPrepare && !connection.getEnablePrepareOnFirstPreparedStatementCall() && !isExecutedAtLeastOnce) { + else if (needsPrepare && !connection.getEnablePrepareOnFirstPreparedStatementCall() + && !isExecutedAtLeastOnce) { buildExecSQLParams(tdsWriter); isExecutedAtLeastOnce = true; } else if (needsPrepare) { // Second execution, use prepared statements since we seem to be re-using it. @@ -1155,11 +1192,40 @@ private boolean doPrepExec(TDSWriter tdsWriter, Parameter[] params, boolean hasN } } - sendParamsByRPC(tdsWriter, params); + sendParamsByRPC(tdsWriter, params, bReturnValueSyntax, callRpcDirectly); return needsPrepare; } + /** + * Checks if we should call RPC directly for stored procedures + * + * @param params + * @return + * @throws SQLServerException + */ + boolean callRPCDirectly(Parameter[] params) throws SQLServerException { + int paramCount = SQLServerConnection.countParams(userSQL); + return (null != procedureName && paramCount != 0 && !isTVPType(params)); + } + + /** + * Checks if the parameter is a TVP type. + * + * @param params + * @return + * @throws SQLServerException + */ + private boolean isTVPType(Parameter[] params) throws SQLServerException { + for (int i = 0; i < params.length; i++) { + if (JDBCType.TVP == params[i].getJdbcType()) { + isTVPType = true; + return true; + } + } + return false; + } + /** * Executes sp_prepare to prepare a parameterized statement and sets the prepared statement handle * @@ -1233,6 +1299,11 @@ private SQLServerResultSet buildExecuteMetaData() throws SQLServerException, SQL * The index specified was outside the number of parameters for the statement. */ final Parameter setterGetParam(int index) throws SQLServerException { + if (!connection.getUseFlexibleCallableStatements() && isSetByName && isSetByIndex) { + SQLServerException.makeFromDriverError(connection, this, + SQLServerException.getErrString("R_noNamedAndIndexedParameters"), null, false); + } + if (index < 1 || index > inOutParam.length) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_indexOutOfRange")); Object[] msgArgs = {index}; @@ -1281,6 +1352,7 @@ final void setSQLXMLInternal(int parameterIndex, SQLXML value) throws SQLServerE @Override public final void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[] {parameterIndex, x}); checkClosed(); @@ -1290,6 +1362,7 @@ public final void setAsciiStream(int parameterIndex, InputStream x) throws SQLEx @Override public final void setAsciiStream(int n, java.io.InputStream x, int length) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[] {n, x, length}); checkClosed(); @@ -1299,6 +1372,7 @@ public final void setAsciiStream(int n, java.io.InputStream x, int length) throw @Override public final void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[] {parameterIndex, x, length}); checkClosed(); @@ -1308,6 +1382,7 @@ public final void setAsciiStream(int parameterIndex, InputStream x, long length) @Override public final void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {parameterIndex, x}); checkClosed(); @@ -1318,6 +1393,7 @@ public final void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLServ @Override public final void setBigDecimal(int parameterIndex, BigDecimal x, int precision, int scale) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {parameterIndex, x, precision, scale}); @@ -1329,6 +1405,7 @@ public final void setBigDecimal(int parameterIndex, BigDecimal x, int precision, @Override public final void setBigDecimal(int parameterIndex, BigDecimal x, int precision, int scale, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {parameterIndex, x, precision, scale, forceEncrypt}); @@ -1339,6 +1416,7 @@ public final void setBigDecimal(int parameterIndex, BigDecimal x, int precision, @Override public final void setMoney(int n, BigDecimal x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setMoney", new Object[] {n, x}); checkClosed(); @@ -1348,6 +1426,7 @@ public final void setMoney(int n, BigDecimal x) throws SQLServerException { @Override public final void setMoney(int n, BigDecimal x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setMoney", new Object[] {n, x, forceEncrypt}); checkClosed(); @@ -1357,6 +1436,7 @@ public final void setMoney(int n, BigDecimal x, boolean forceEncrypt) throws SQL @Override public final void setSmallMoney(int n, BigDecimal x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setSmallMoney", new Object[] {n, x}); checkClosed(); @@ -1366,6 +1446,7 @@ public final void setSmallMoney(int n, BigDecimal x) throws SQLServerException { @Override public final void setSmallMoney(int n, BigDecimal x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setSmallMoney", new Object[] {n, x, forceEncrypt}); checkClosed(); @@ -1375,6 +1456,7 @@ public final void setSmallMoney(int n, BigDecimal x, boolean forceEncrypt) throw @Override public final void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBinaryStreaml", new Object[] {parameterIndex, x}); checkClosed(); @@ -1384,6 +1466,7 @@ public final void setBinaryStream(int parameterIndex, InputStream x) throws SQLE @Override public final void setBinaryStream(int n, java.io.InputStream x, int length) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[] {n, x, length}); checkClosed(); @@ -1393,6 +1476,7 @@ public final void setBinaryStream(int n, java.io.InputStream x, int length) thro @Override public final void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[] {parameterIndex, x, length}); checkClosed(); @@ -1402,6 +1486,7 @@ public final void setBinaryStream(int parameterIndex, InputStream x, long length @Override public final void setBoolean(int n, boolean x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBoolean", new Object[] {n, x}); checkClosed(); @@ -1411,6 +1496,7 @@ public final void setBoolean(int n, boolean x) throws SQLServerException { @Override public final void setBoolean(int n, boolean x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBoolean", new Object[] {n, x, forceEncrypt}); checkClosed(); @@ -1420,6 +1506,7 @@ public final void setBoolean(int n, boolean x, boolean forceEncrypt) throws SQLS @Override public final void setByte(int n, byte x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setByte", new Object[] {n, x}); checkClosed(); @@ -1429,6 +1516,7 @@ public final void setByte(int n, byte x) throws SQLServerException { @Override public final void setByte(int n, byte x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setByte", new Object[] {n, x, forceEncrypt}); checkClosed(); @@ -1438,6 +1526,7 @@ public final void setByte(int n, byte x, boolean forceEncrypt) throws SQLServerE @Override public final void setBytes(int n, byte[] x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBytes", new Object[] {n, x}); checkClosed(); @@ -1447,6 +1536,7 @@ public final void setBytes(int n, byte[] x) throws SQLServerException { @Override public final void setBytes(int n, byte[] x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBytes", new Object[] {n, x, forceEncrypt}); checkClosed(); @@ -1456,6 +1546,7 @@ public final void setBytes(int n, byte[] x, boolean forceEncrypt) throws SQLServ @Override public final void setUniqueIdentifier(int index, String guid) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setUniqueIdentifier", new Object[] {index, guid}); checkClosed(); @@ -1465,6 +1556,7 @@ public final void setUniqueIdentifier(int index, String guid) throws SQLServerEx @Override public final void setUniqueIdentifier(int index, String guid, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setUniqueIdentifier", new Object[] {index, guid, forceEncrypt}); @@ -1475,6 +1567,7 @@ public final void setUniqueIdentifier(int index, String guid, boolean forceEncry @Override public final void setDouble(int n, double x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDouble", new Object[] {n, x}); checkClosed(); @@ -1484,6 +1577,7 @@ public final void setDouble(int n, double x) throws SQLServerException { @Override public final void setDouble(int n, double x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDouble", new Object[] {n, x, forceEncrypt}); checkClosed(); @@ -1493,6 +1587,7 @@ public final void setDouble(int n, double x, boolean forceEncrypt) throws SQLSer @Override public final void setFloat(int n, float x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setFloat", new Object[] {n, x}); checkClosed(); @@ -1502,6 +1597,7 @@ public final void setFloat(int n, float x) throws SQLServerException { @Override public final void setFloat(int n, float x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setFloat", new Object[] {n, x, forceEncrypt}); checkClosed(); @@ -1511,6 +1607,7 @@ public final void setFloat(int n, float x, boolean forceEncrypt) throws SQLServe @Override public final void setGeometry(int n, Geometry x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setGeometry", new Object[] {n, x}); checkClosed(); @@ -1520,6 +1617,7 @@ public final void setGeometry(int n, Geometry x) throws SQLServerException { @Override public final void setGeography(int n, Geography x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setGeography", new Object[] {n, x}); checkClosed(); @@ -1529,6 +1627,7 @@ public final void setGeography(int n, Geography x) throws SQLServerException { @Override public final void setInt(int n, int value) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setInt", new Object[] {n, value}); checkClosed(); @@ -1538,6 +1637,7 @@ public final void setInt(int n, int value) throws SQLServerException { @Override public final void setInt(int n, int value, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setInt", new Object[] {n, value, forceEncrypt}); checkClosed(); @@ -1547,6 +1647,7 @@ public final void setInt(int n, int value, boolean forceEncrypt) throws SQLServe @Override public final void setLong(int n, long x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setLong", new Object[] {n, x}); checkClosed(); @@ -1556,6 +1657,7 @@ public final void setLong(int n, long x) throws SQLServerException { @Override public final void setLong(int n, long x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setLong", new Object[] {n, x, forceEncrypt}); checkClosed(); @@ -1565,6 +1667,7 @@ public final void setLong(int n, long x, boolean forceEncrypt) throws SQLServerE @Override public final void setNull(int index, int jdbcType) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNull", new Object[] {index, jdbcType}); checkClosed(); @@ -1610,6 +1713,7 @@ final void setObjectNoType(int index, Object obj, boolean forceEncrypt) throws S @Override public final void setObject(int index, Object obj) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {index, obj}); checkClosed(); @@ -1619,6 +1723,7 @@ public final void setObject(int index, Object obj) throws SQLServerException { @Override public final void setObject(int n, Object obj, int jdbcType) throws SQLServerException { + isSetByIndex = true; String tvpName = null; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {n, obj, jdbcType}); @@ -1633,6 +1738,7 @@ public final void setObject(int n, Object obj, int jdbcType) throws SQLServerExc @Override public final void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {parameterIndex, x, targetSqlType, scaleOrLength}); @@ -1656,6 +1762,7 @@ public final void setObject(int parameterIndex, Object x, int targetSqlType, @Override public final void setObject(int parameterIndex, Object x, int targetSqlType, Integer precision, int scale) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {parameterIndex, x, targetSqlType, precision, scale}); @@ -1677,6 +1784,7 @@ public final void setObject(int parameterIndex, Object x, int targetSqlType, Int @Override public final void setObject(int parameterIndex, Object x, int targetSqlType, Integer precision, int scale, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {parameterIndex, x, targetSqlType, precision, scale, forceEncrypt}); @@ -1774,6 +1882,7 @@ public final void setObject(int parameterIndex, Object x, SQLType targetSqlType, @Override public final void setShort(int index, short x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setShort", new Object[] {index, x}); checkClosed(); @@ -1783,6 +1892,7 @@ public final void setShort(int index, short x) throws SQLServerException { @Override public final void setShort(int index, short x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setShort", new Object[] {index, x, forceEncrypt}); checkClosed(); @@ -1792,6 +1902,7 @@ public final void setShort(int index, short x, boolean forceEncrypt) throws SQLS @Override public final void setString(int index, String str) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setString", new Object[] {index, str}); checkClosed(); @@ -1801,6 +1912,7 @@ public final void setString(int index, String str) throws SQLServerException { @Override public final void setString(int index, String str, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setString", new Object[] {index, str, forceEncrypt}); checkClosed(); @@ -1810,6 +1922,7 @@ public final void setString(int index, String str, boolean forceEncrypt) throws @Override public final void setNString(int parameterIndex, String value) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNString", new Object[] {parameterIndex, value}); checkClosed(); @@ -1819,6 +1932,7 @@ public final void setNString(int parameterIndex, String value) throws SQLExcepti @Override public final void setNString(int parameterIndex, String value, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNString", new Object[] {parameterIndex, value, forceEncrypt}); @@ -1829,6 +1943,7 @@ public final void setNString(int parameterIndex, String value, boolean forceEncr @Override public final void setTime(int n, java.sql.Time x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {n, x}); checkClosed(); @@ -1838,6 +1953,7 @@ public final void setTime(int n, java.sql.Time x) throws SQLServerException { @Override public final void setTime(int n, java.sql.Time x, int scale) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {n, x, scale}); checkClosed(); @@ -1847,6 +1963,7 @@ public final void setTime(int n, java.sql.Time x, int scale) throws SQLServerExc @Override public final void setTime(int n, java.sql.Time x, int scale, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {n, x, scale, forceEncrypt}); checkClosed(); @@ -1856,6 +1973,7 @@ public final void setTime(int n, java.sql.Time x, int scale, boolean forceEncryp @Override public final void setTimestamp(int n, java.sql.Timestamp x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {n, x}); checkClosed(); @@ -1865,6 +1983,7 @@ public final void setTimestamp(int n, java.sql.Timestamp x) throws SQLServerExce @Override public final void setTimestamp(int n, java.sql.Timestamp x, int scale) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {n, x, scale}); checkClosed(); @@ -1875,6 +1994,7 @@ public final void setTimestamp(int n, java.sql.Timestamp x, int scale) throws SQ @Override public final void setTimestamp(int n, java.sql.Timestamp x, int scale, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {n, x, scale, forceEncrypt}); checkClosed(); @@ -1884,6 +2004,7 @@ public final void setTimestamp(int n, java.sql.Timestamp x, int scale, @Override public final void setDateTimeOffset(int n, microsoft.sql.DateTimeOffset x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {n, x}); checkClosed(); @@ -1893,6 +2014,7 @@ public final void setDateTimeOffset(int n, microsoft.sql.DateTimeOffset x) throw @Override public final void setDateTimeOffset(int n, microsoft.sql.DateTimeOffset x, int scale) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {n, x, scale}); checkClosed(); @@ -1903,6 +2025,7 @@ public final void setDateTimeOffset(int n, microsoft.sql.DateTimeOffset x, int s @Override public final void setDateTimeOffset(int n, microsoft.sql.DateTimeOffset x, int scale, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {n, x, scale, forceEncrypt}); @@ -1913,6 +2036,7 @@ public final void setDateTimeOffset(int n, microsoft.sql.DateTimeOffset x, int s @Override public final void setDate(int n, java.sql.Date x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDate", new Object[] {n, x}); checkClosed(); @@ -1922,6 +2046,7 @@ public final void setDate(int n, java.sql.Date x) throws SQLServerException { @Override public final void setDateTime(int n, java.sql.Timestamp x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDateTime", new Object[] {n, x}); checkClosed(); @@ -1931,6 +2056,7 @@ public final void setDateTime(int n, java.sql.Timestamp x) throws SQLServerExcep @Override public final void setDateTime(int n, java.sql.Timestamp x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDateTime", new Object[] {n, x, forceEncrypt}); checkClosed(); @@ -1940,6 +2066,7 @@ public final void setDateTime(int n, java.sql.Timestamp x, boolean forceEncrypt) @Override public final void setSmallDateTime(int n, java.sql.Timestamp x) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setSmallDateTime", new Object[] {n, x}); checkClosed(); @@ -1949,6 +2076,7 @@ public final void setSmallDateTime(int n, java.sql.Timestamp x) throws SQLServer @Override public final void setSmallDateTime(int n, java.sql.Timestamp x, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setSmallDateTime", new Object[] {n, x, forceEncrypt}); checkClosed(); @@ -1958,6 +2086,7 @@ public final void setSmallDateTime(int n, java.sql.Timestamp x, boolean forceEnc @Override public final void setStructured(int n, String tvpName, SQLServerDataTable tvpDataTable) throws SQLServerException { + isSetByIndex = true; tvpName = getTVPNameIfNull(n, tvpName); if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[] {n, tvpName, tvpDataTable}); @@ -1968,6 +2097,7 @@ public final void setStructured(int n, String tvpName, SQLServerDataTable tvpDat @Override public final void setStructured(int n, String tvpName, ResultSet tvpResultSet) throws SQLServerException { + isSetByIndex = true; tvpName = getTVPNameIfNull(n, tvpName); if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[] {n, tvpName, tvpResultSet}); @@ -1979,6 +2109,7 @@ public final void setStructured(int n, String tvpName, ResultSet tvpResultSet) t @Override public final void setStructured(int n, String tvpName, ISQLServerDataRecord tvpBulkRecord) throws SQLServerException { + isSetByIndex = true; tvpName = getTVPNameIfNull(n, tvpName); if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[] {n, tvpName, tvpBulkRecord}); @@ -2991,7 +3122,7 @@ final void doExecutePreparedStatementBatch(PrepStmtBatchExecCmd batchCommand) th resetForReexecute(); tdsWriter = batchCommand.startRequest(TDS.PKT_RPC); buildExecParams(tdsWriter); - sendParamsByRPC(tdsWriter, batchParam); + sendParamsByRPC(tdsWriter, batchParam, bReturnValueSyntax, false); ensureExecuteResultsReader( batchCommand.startResponse(getIsResponseBufferingAdaptive())); startResults(); @@ -3085,6 +3216,7 @@ public final boolean getUseFmtOnly() throws SQLServerException { @Override public final void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[] {parameterIndex, reader}); checkClosed(); @@ -3094,6 +3226,7 @@ public final void setCharacterStream(int parameterIndex, Reader reader) throws S @Override public final void setCharacterStream(int n, java.io.Reader reader, int length) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[] {n, reader, length}); checkClosed(); @@ -3103,6 +3236,7 @@ public final void setCharacterStream(int n, java.io.Reader reader, int length) t @Override public final void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[] {parameterIndex, reader, length}); @@ -3113,6 +3247,7 @@ public final void setCharacterStream(int parameterIndex, Reader reader, long len @Override public final void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNCharacterStream", new Object[] {parameterIndex, value}); checkClosed(); @@ -3122,6 +3257,7 @@ public final void setNCharacterStream(int parameterIndex, Reader value) throws S @Override public final void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNCharacterStream", new Object[] {parameterIndex, value, length}); @@ -3137,6 +3273,7 @@ public final void setRef(int i, java.sql.Ref x) throws SQLException { @Override public final void setBlob(int i, java.sql.Blob x) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[] {i, x}); checkClosed(); @@ -3146,6 +3283,7 @@ public final void setBlob(int i, java.sql.Blob x) throws SQLException { @Override public final void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[] {parameterIndex, inputStream}); checkClosed(); @@ -3156,6 +3294,7 @@ public final void setBlob(int parameterIndex, InputStream inputStream) throws SQ @Override public final void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[] {parameterIndex, inputStream, length}); @@ -3166,6 +3305,7 @@ public final void setBlob(int parameterIndex, InputStream inputStream, long leng @Override public final void setClob(int parameterIndex, java.sql.Clob clobValue) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setClob", new Object[] {parameterIndex, clobValue}); checkClosed(); @@ -3175,6 +3315,7 @@ public final void setClob(int parameterIndex, java.sql.Clob clobValue) throws SQ @Override public final void setClob(int parameterIndex, Reader reader) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setClob", new Object[] {parameterIndex, reader}); checkClosed(); @@ -3184,6 +3325,7 @@ public final void setClob(int parameterIndex, Reader reader) throws SQLException @Override public final void setClob(int parameterIndex, Reader reader, long length) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setClob", new Object[] {parameterIndex, reader, length}); checkClosed(); @@ -3193,6 +3335,7 @@ public final void setClob(int parameterIndex, Reader reader, long length) throws @Override public final void setNClob(int parameterIndex, NClob value) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterIndex, value}); checkClosed(); @@ -3202,6 +3345,7 @@ public final void setNClob(int parameterIndex, NClob value) throws SQLException @Override public final void setNClob(int parameterIndex, Reader reader) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterIndex, reader}); checkClosed(); @@ -3211,6 +3355,7 @@ public final void setNClob(int parameterIndex, Reader reader) throws SQLExceptio @Override public final void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterIndex, reader, length}); checkClosed(); @@ -3225,6 +3370,7 @@ public final void setArray(int i, java.sql.Array x) throws SQLException { @Override public final void setDate(int n, java.sql.Date x, java.util.Calendar cal) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDate", new Object[] {n, x, cal}); checkClosed(); @@ -3235,6 +3381,7 @@ public final void setDate(int n, java.sql.Date x, java.util.Calendar cal) throws @Override public final void setDate(int n, java.sql.Date x, java.util.Calendar cal, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setDate", new Object[] {n, x, cal, forceEncrypt}); checkClosed(); @@ -3244,6 +3391,7 @@ public final void setDate(int n, java.sql.Date x, java.util.Calendar cal, @Override public final void setTime(int n, java.sql.Time x, java.util.Calendar cal) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {n, x, cal}); checkClosed(); @@ -3254,6 +3402,7 @@ public final void setTime(int n, java.sql.Time x, java.util.Calendar cal) throws @Override public final void setTime(int n, java.sql.Time x, java.util.Calendar cal, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {n, x, cal, forceEncrypt}); checkClosed(); @@ -3263,6 +3412,7 @@ public final void setTime(int n, java.sql.Time x, java.util.Calendar cal, @Override public final void setTimestamp(int n, java.sql.Timestamp x, java.util.Calendar cal) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {n, x, cal}); checkClosed(); @@ -3273,6 +3423,7 @@ public final void setTimestamp(int n, java.sql.Timestamp x, java.util.Calendar c @Override public final void setTimestamp(int n, java.sql.Timestamp x, java.util.Calendar cal, boolean forceEncrypt) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {n, x, cal, forceEncrypt}); checkClosed(); @@ -3282,6 +3433,7 @@ public final void setTimestamp(int n, java.sql.Timestamp x, java.util.Calendar c @Override public final void setNull(int paramIndex, int sqlType, String typeName) throws SQLServerException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setNull", new Object[] {paramIndex, sqlType, typeName}); checkClosed(); @@ -3331,6 +3483,7 @@ public final void setRowId(int parameterIndex, RowId x) throws SQLException { @Override public final void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { + isSetByIndex = true; if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "setSQLXML", new Object[] {parameterIndex, xmlObject}); checkClosed(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index f4fd2a253..9055e9add 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -144,6 +144,8 @@ protected Object[][] getContents() { {"R_stringNotInHex", "The string is not in a valid hex format."}, {"R_unknownType", "The Java type {0} is not a supported type."}, {"R_physicalConnectionIsClosed", "The physical connection is closed for this pooled connection."}, + {"R_noNamedAndIndexedParameters", "Detected uncompliant use of both named and indexed parameters while 'useFlexibleCallableStatements=false'. It is suggested to either exclusively use named parameters or indexed parameters."}, + {"R_unknownOutputParameter", "Cannot acquire output parameter value by name. No parameter index was associated with the output parameter name. If acquiring output parameter by name, verify that the output parameter was initially registered by name."}, {"R_invalidDataSourceReference", "Invalid DataSource reference."}, {"R_cantGetColumnValueFromDeletedRow", "Cannot get a value from a deleted row."}, {"R_cantGetUpdatedColumnValue", "Updated columns cannot be accessed until updateRow() or cancelRowUpdates() has been called."}, @@ -198,6 +200,7 @@ protected Object[][] getContents() { {"R_disableStatementPoolingPropertyDescription", "Disables the statement pooling feature."}, {"R_integratedSecurityPropertyDescription", "Indicates whether Windows authentication will be used to connect to SQL Server."}, {"R_useDefaultGSSCredentialPropertyDescription", "Indicates whether GSSCredential will be created using native GSS-API."}, + {"R_useFlexibleCallableStatementsPropertyDescription", "Indicates whether sp_sproc_columns will be used for parameter name lookup when setting or registering parameters for callable statements."}, {"R_authenticationSchemePropertyDescription", "The authentication scheme to be used for integrated authentication."}, {"R_lockTimeoutPropertyDescription", "The number of milliseconds to wait before the database reports a lock time-out."}, {"R_connectRetryCountPropertyDescription", "The number of reconnection attempts if there is a connection failure."}, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index d9dfdfdb6..2fe360514 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -64,7 +64,15 @@ public class SQLServerStatement implements ISQLServerStatement { private static final String ACTIVITY_ID = " ActivityId: "; /** response buffer adaptive flag */ - private boolean isResponseBufferingAdaptive = false; + boolean isResponseBufferingAdaptive = false; + + /** TDS token return value status **/ + int returnValueStatus; + + /** Check if statement contains TVP Type */ + boolean isTVPType = false; + + static int userDefinedFunctionReturnStatus = 2; final boolean getIsResponseBufferingAdaptive() { return isResponseBufferingAdaptive; @@ -112,10 +120,13 @@ final boolean wasExecuted() { return null != tdsReader; } + /** Return parameter for stored procedure calls */ + transient Parameter returnParam; + /** * The input and out parameters for statement execution. */ - transient Parameter[] inOutParam; // Parameters for prepared stmts and stored procedures + transient Parameter[] inOutParam = null; // Parameters for prepared stmts and stored procedures /** * The statement's connection. @@ -138,6 +149,12 @@ final boolean wasExecuted() { */ boolean isCloseOnCompletion = false; + /** Checks if the callable statement's parameters are set by name **/ + protected boolean isSetByName = false; + + /** Checks if the prepared statement's parameters were set by index **/ + protected boolean isSetByIndex = false; + /** * Currently executing or most recently executed TDSCommand (statement cmd, server cursor cmd, ...) subject to * cancellation through Statement.cancel. @@ -1609,6 +1626,14 @@ boolean onRetStatus(TDSReader tdsReader) throws SQLServerException { else { procedureRetStatToken = new StreamRetStatus(); procedureRetStatToken.setFromTDS(tdsReader); + // Only read the return value from stored procedure if we are expecting one. Also, check that it is + // not cursorable and not TVP type. For these two, the driver is still following the old behavior of + // executing sp_executesql for stored procedures. + if (!isCursorable(executeMethod) && !isTVPType && null != inOutParam + && inOutParam.length > 0 && inOutParam[0].isReturnValue()) { + inOutParam[0].setFromReturnStatus(procedureRetStatToken.getStatus(), connection); + return false; + } } return true; @@ -1616,11 +1641,21 @@ boolean onRetStatus(TDSReader tdsReader) throws SQLServerException { @Override boolean onRetValue(TDSReader tdsReader) throws SQLServerException { + // Status: A value of 0x01 means the return value corresponds to an output parameter from + // a stored procedure. If it's 0x02 then the value corresponds to a return value from a + // user defined function. + // + // If it's a return value from a user defined function, we need to return false from this method + // so that the return value is not skipped. + int status = tdsReader.peekReturnValueStatus(); + + SQLServerStatement.this.returnValueStatus = status; + // We are only interested in return values that are statement OUT parameters, // in which case we need to stop parsing and let CallableStatement take over. // A RETVALUE token appearing in the execution results, but before any RETSTATUS // token, is a TEXTPTR return value that should be ignored. - if (moreResults && null == procedureRetStatToken) { + if (moreResults && null == procedureRetStatToken && status != userDefinedFunctionReturnStatus) { Parameter p = new Parameter( Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, connection)); p.skipRetValStatus(tdsReader); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java index 7f9c44a2f..6d82bf836 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java @@ -417,8 +417,8 @@ private XAReturnValue dtc_XA_interface(int nType, Xid xid, int xaFlags) throws X initCS = (SQLServerCallableStatement) controlConnection .prepareCall("{call master..xp_sqljdbc_xa_init_ex(?, ?,?)}"); initCS.registerOutParameter(1, Types.INTEGER); // Return status - initCS.registerOutParameter(2, Types.CHAR); // Return error message - initCS.registerOutParameter(3, Types.CHAR); // Return version number + initCS.registerOutParameterNonPLP(2, Types.CHAR); // Return error message + initCS.registerOutParameterNonPLP(3, Types.CHAR); // Return version number try { initCS.execute(); } catch (SQLServerException eX) { @@ -528,21 +528,21 @@ private XAReturnValue dtc_XA_interface(int nType, Xid xid, int xaFlags) throws X sContext = "START:"; cs = getXACallableStatementHandle(XA_START); cs.registerOutParameter(n++, Types.INTEGER); // Return status - cs.registerOutParameter(n++, Types.CHAR); // Return error message + cs.registerOutParameterNonPLP(n++, Types.CHAR); // Return error message cs.setBytes(n++, gid); // Global XID cs.setBytes(n++, bid); // Branch ID cs.setInt(n++, xaFlags); // XA transaction flags - cs.registerOutParameter(n++, Types.BINARY); // Returned OLE transaction cookie + cs.registerOutParameterNonPLP(n++, Types.BINARY); // Returned OLE transaction cookie cs.setInt(n++, timeoutSeconds); // Transaction timeout in seconds. cs.setInt(n++, formatId); // Format ID - cs.registerOutParameter(n++, Types.CHAR); // DLL Version number + cs.registerOutParameterNonPLP(n++, Types.CHAR); // DLL Version number cs.setInt(n++, Integer.parseInt(version)); // Version of SQL Server cs.setInt(n++, instanceName.length()); // Length of SQL Server instance name cs.setBytes(n++, instanceName.getBytes()); // SQL Server instance name cs.setInt(n++, architectureMSSQL); // Architecture of SQL Server cs.setInt(n++, architectureOS); // Architecture of OS running SQL Server cs.setInt(n++, isTransacrionTimeoutSet); // pass 1 if setTransactionTimeout() is called - cs.registerOutParameter(n++, Types.BINARY); // Return UoW + cs.registerOutParameterNonPLP(n++, Types.BINARY); // Return UoW break; @@ -550,12 +550,12 @@ private XAReturnValue dtc_XA_interface(int nType, Xid xid, int xaFlags) throws X sContext = "END:"; cs = getXACallableStatementHandle(XA_END); cs.registerOutParameter(n++, Types.INTEGER); - cs.registerOutParameter(n++, Types.CHAR); + cs.registerOutParameterNonPLP(n++, Types.CHAR); cs.setBytes(n++, gid); cs.setBytes(n++, bid); cs.setInt(n++, xaFlags); cs.setInt(n++, formatId); - cs.registerOutParameter(n++, Types.BINARY); // Return UoW + cs.registerOutParameterNonPLP(n++, Types.BINARY); // Return UoW break; case XA_PREPARE: @@ -566,7 +566,7 @@ private XAReturnValue dtc_XA_interface(int nType, Xid xid, int xaFlags) throws X cs = getXACallableStatementHandle(XA_PREPARE); cs.registerOutParameter(n++, Types.INTEGER); - cs.registerOutParameter(n++, Types.CHAR); + cs.registerOutParameterNonPLP(n++, Types.CHAR); cs.setBytes(n++, gid); cs.setBytes(n++, bid); if ((SSTRANSTIGHTLYCPLD & xaFlags) == SSTRANSTIGHTLYCPLD) @@ -578,7 +578,7 @@ private XAReturnValue dtc_XA_interface(int nType, Xid xid, int xaFlags) throws X sContext = "COMMIT:"; cs = getXACallableStatementHandle(XA_COMMIT); cs.registerOutParameter(n++, Types.INTEGER); - cs.registerOutParameter(n++, Types.CHAR); + cs.registerOutParameterNonPLP(n++, Types.CHAR); cs.setBytes(n++, gid); cs.setBytes(n++, bid); cs.setInt(n++, xaFlags); @@ -593,7 +593,7 @@ private XAReturnValue dtc_XA_interface(int nType, Xid xid, int xaFlags) throws X cs = getXACallableStatementHandle(XA_ROLLBACK); cs.registerOutParameter(n++, Types.INTEGER); - cs.registerOutParameter(n++, Types.CHAR); + cs.registerOutParameterNonPLP(n++, Types.CHAR); cs.setBytes(n++, gid); cs.setBytes(n++, bid); if ((SSTRANSTIGHTLYCPLD & xaFlags) == SSTRANSTIGHTLYCPLD) @@ -608,7 +608,7 @@ private XAReturnValue dtc_XA_interface(int nType, Xid xid, int xaFlags) throws X else cs = getXACallableStatementHandle(XA_FORGET); cs.registerOutParameter(n++, Types.INTEGER); - cs.registerOutParameter(n++, Types.CHAR); + cs.registerOutParameterNonPLP(n++, Types.CHAR); cs.setBytes(n++, gid); cs.setBytes(n++, bid); if ((SSTRANSTIGHTLYCPLD & xaFlags) == SSTRANSTIGHTLYCPLD) @@ -620,9 +620,9 @@ private XAReturnValue dtc_XA_interface(int nType, Xid xid, int xaFlags) throws X sContext = "RECOVER:"; cs = getXACallableStatementHandle(XA_RECOVER); cs.registerOutParameter(n++, Types.INTEGER); - cs.registerOutParameter(n++, Types.CHAR); + cs.registerOutParameterNonPLP(n++, Types.CHAR); cs.setInt(n++, xaFlags); - cs.registerOutParameter(n++, Types.BINARY); + cs.registerOutParameterNonPLP(n++, Types.BINARY); // Format Id need not be sent for recover action break; default: diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java index ab76347b4..1be900a36 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java @@ -132,6 +132,8 @@ final class DTV { int valueLength = 0; boolean sendStringParametersAsUnicode = true; + boolean isNonPLP = false; + /** * Sets a DTV value from a Java object. * @@ -289,7 +291,7 @@ final class SendByRPCOp extends DTVExecuteOp { } void execute(DTV dtv, String strValue) throws SQLServerException { - tdsWriter.writeRPCStringUnicode(name, strValue, isOutParam, collation); + tdsWriter.writeRPCStringUnicode(name, strValue, isOutParam, collation, dtv.isNonPLP); } void execute(DTV dtv, Clob clobValue) throws SQLServerException { @@ -311,7 +313,7 @@ void execute(DTV dtv, Clob clobValue) throws SQLServerException { if (null != collation && (JDBCType.CHAR == jdbcType || JDBCType.VARCHAR == jdbcType || JDBCType.LONGVARCHAR == jdbcType || JDBCType.CLOB == jdbcType)) { if (null == clobReader) { - tdsWriter.writeRPCByteArray(name, null, isOutParam, jdbcType, collation); + tdsWriter.writeRPCByteArray(name, null, isOutParam, jdbcType, collation, false); } else { ReaderInputStream clobStream = new ReaderInputStream(clobReader, collation.getCharset(), clobLength); @@ -322,7 +324,7 @@ void execute(DTV dtv, Clob clobValue) throws SQLServerException { } else // Send CLOB value as Unicode { if (null == clobReader) { - tdsWriter.writeRPCStringUnicode(name, null, isOutParam, collation); + tdsWriter.writeRPCStringUnicode(name, null, isOutParam, collation, false); } else { tdsWriter.writeRPCReaderUnicode(name, clobReader, clobLength, isOutParam, collation); } @@ -845,14 +847,41 @@ private void sendTemporal(DTV dtv, JavaType javaType, Object value) throws SQLSe switch (jdbcType) { case DATETIME: + if (null != cryptoMeta) { + tdsWriter.writeEncryptedRPCDateTime(name, + timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), + subSecondNanos, isOutParam, jdbcType, statement); + } else { + if (conn.getDatetimeParameterType().equals(DatetimeType.DATETIME2.toString())) { + tdsWriter.writeRPCDateTime2(name, + timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), + subSecondNanos, TDS.DEFAULT_FRACTIONAL_SECONDS_SCALE, isOutParam); + } else if (conn.getDatetimeParameterType() + .equals(DatetimeType.DATETIME.toString())) { + tdsWriter.writeRPCDateTime(name, + timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), + subSecondNanos, isOutParam); + } + } + + break; + case SMALLDATETIME: + if (null != cryptoMeta) { + tdsWriter.writeEncryptedRPCDateTime(name, + timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), + subSecondNanos, isOutParam, jdbcType, statement); + } else { + tdsWriter.writeRPCDateTime(name, + timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), + subSecondNanos, isOutParam); + } + + break; + case TIMESTAMP: if (null != cryptoMeta) { - if ((JDBCType.DATETIME == jdbcType) || (JDBCType.SMALLDATETIME == jdbcType)) { - tdsWriter.writeEncryptedRPCDateTime(name, - timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), - subSecondNanos, isOutParam, jdbcType, statement); - } else if (0 == valueLength) { + if (0 == valueLength) { tdsWriter.writeEncryptedRPCDateTime2(name, timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), subSecondNanos, outScale, isOutParam, statement); @@ -861,10 +890,11 @@ private void sendTemporal(DTV dtv, JavaType javaType, Object value) throws SQLSe timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), subSecondNanos, (valueLength), isOutParam, statement); } - } else + } else { tdsWriter.writeRPCDateTime2(name, timestampNormalizedCalendar(calendar, javaType, conn.baseYear()), subSecondNanos, TDS.MAX_FRACTIONAL_SECONDS_SCALE, isOutParam); + } break; @@ -1074,7 +1104,7 @@ void execute(DTV dtv, BigDecimal bigDecimalValue) throws SQLServerException { DriverError.NOT_SET, null); } else { String strValue = bigDecimalValue.toString(); - tdsWriter.writeRPCStringUnicode(name, strValue, isOutParam, collation); + tdsWriter.writeRPCStringUnicode(name, strValue, isOutParam, collation, false); } } else { tdsWriter.writeRPCBigDecimal(name, bigDecimalValue, outScale, isOutParam); @@ -1125,7 +1155,8 @@ void execute(DTV dtv, byte[] byteArrayValue) throws SQLServerException { } } else - tdsWriter.writeRPCByteArray(name, byteArrayValue, isOutParam, dtv.getJdbcType(), collation); + tdsWriter.writeRPCByteArray(name, byteArrayValue, isOutParam, dtv.getJdbcType(), collation, + dtv.isNonPLP); } @@ -1378,7 +1409,7 @@ void execute(DTV dtv, Blob blobValue) throws SQLServerException { } if (null == blobStream) { - tdsWriter.writeRPCByteArray(name, null, isOutParam, dtv.getJdbcType(), collation); + tdsWriter.writeRPCByteArray(name, null, isOutParam, dtv.getJdbcType(), collation, false); } else { tdsWriter.writeRPCInputStream(name, blobStream, blobLength, isOutParam, dtv.getJdbcType(), collation); } @@ -2319,8 +2350,10 @@ Object getValue(DTV dtv, JDBCType jdbcType, int scale, InputStreamGetterArgs str TypeInfo typeInfo, CryptoMetadata cryptoMetadata, TDSReader tdsReader, SQLServerStatement statement) throws SQLServerException { // Client side type conversion is not supported - if (this.jdbcType != jdbcType) + // Checking for sql_variant here since the check will be performed elsewhere. + if (this.jdbcType != jdbcType && jdbcType != JDBCType.SQL_VARIANT) { DataTypes.throwConversionError(this.jdbcType.toString(), jdbcType.toString()); + } return value; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java index a26133f69..df50180b6 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/tdsparser.java @@ -232,6 +232,17 @@ boolean onRetStatus(TDSReader tdsReader) throws SQLServerException { } boolean onRetValue(TDSReader tdsReader) throws SQLServerException { + // Very unlikely to return true. If we do, it was because any return values in the + // tds response were never read after the RPC. If they were never read, it's safe to skip + // them here + if (this.logContext.equals("ExecDoneHandler")) { + Parameter param = new Parameter(false); + param.skipRetValStatus(tdsReader); + param.skipValue(tdsReader, true); + + return true; + } + TDSParser.throwUnexpectedTokenException(tdsReader, logContext); return false; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java index f44962367..9736254d4 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java @@ -205,6 +205,9 @@ public void testDataSource() throws SQLServerException { assertEquals(booleanPropValue, ds.getUseDefaultGSSCredential(), TestResource.getResource("R_valuesAreDifferent")); + ds.setUseFlexibleCallableStatements(booleanPropValue); + assertEquals(booleanPropValue, ds.getUseFlexibleCallableStatements(), + TestResource.getResource("R_valuesAreDifferent")); ds.setCalcBigDecimalScale(booleanPropValue); assertEquals(booleanPropValue, ds.getCalcBigDecimalScale(), TestResource.getResource("R_valuesAreDifferent")); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index 9a4c4177b..449ea1f0b 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -43,7 +43,8 @@ protected Object[][] getContents() { {"R_lengthTruncated", " The inserted length is truncated or not correct!"}, {"R_timeValueTruncated", " The time value is truncated or not correct!"}, {"R_invalidErrorMessage", "Invalid Error Message: "}, - {"R_kerberosNativeGSSFailure", "No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)"}, + {"R_kerberosNativeGSSFailure", + "No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)"}, {"R_expectedFailPassed", "Expected failure did not fail"}, {"R_dataTypeNotFound", "Cannot find data type"}, {"R_illegalCharWktPosition", "Illegal character in Well-Known text at position {0}."}, {"R_illegalCharWkt", "Illegal Well-Known text. Please make sure Well-Known text is valid."}, @@ -55,6 +56,8 @@ protected Object[][] getContents() { {"R_createDropAlterTableFailed", "Create/drop/alter table with preparedStatement failed!"}, {"R_grantFailed", "grant table with preparedStatement failed!"}, {"R_connectionIsClosed", "The connection is closed."}, + {"R_noNamedAndIndexedParameters", "Detected uncompliant use of both named and indexed parameters while 'useFlexibleCallableStatements=false'. It is suggested to either exclusively use named parameters or indexed parameters."}, + {"R_unknownOutputParameter", "Cannot acquire output parameter value by name. No parameter index was associated with the output parameter name. If acquiring output parameter by name, verify that the output parameter was initially registered by name."}, {"R_ConnectionURLNull", "The connection URL is null."}, {"R_connectionIsNotClosed", "The connection is not closed."}, {"R_invalidExceptionMessage", "Invalid exception message"}, @@ -63,6 +66,7 @@ protected Object[][] getContents() { {"R_deadConnection", "Dead connection should be invalid"}, {"R_wrongExceptionMessage", "Wrong exception message"}, {"R_wrongSqlState", "Wrong sql state"}, {"R_parameterNotDefined", "Parameter {0} was not defined"}, + {"R_notValidParameterForProcedure", "{0} is not a parameter for procedure {1}."}, {"R_unexpectedExceptionContent", "Unexpected content in exception message"}, {"R_connectionClosed", "The connection has been closed"}, {"R_conversionFailed", "Conversion failed when converting {0} to {1} data type"}, @@ -204,6 +208,7 @@ protected Object[][] getContents() { {"R_connectTimedOut", "connect timed out"}, {"R_sessionKilled", "Cannot continue the execution because the session is in the kill state"}, {"R_failedFedauth", "Failed to acquire fedauth token: "}, - {"R_noLoginModulesConfiguredForJdbcDriver", "javax.security.auth.login.LoginException (No LoginModules configured for SQLJDBCDriver)"}, + {"R_noLoginModulesConfiguredForJdbcDriver", + "javax.security.auth.login.LoginException (No LoginModules configured for SQLJDBCDriver)"}, {"R_unexpectedThreadCount", "Thread count is higher than expected."}}; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java index 239ae1f71..e9d73b795 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java @@ -1,5 +1,6 @@ package com.microsoft.sqlserver.jdbc.callablestatement; +import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; @@ -21,7 +22,7 @@ import java.util.TimeZone; import java.util.UUID; -import com.microsoft.sqlserver.testframework.PrepUtil; +import org.junit.Assert; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; @@ -34,10 +35,14 @@ import com.microsoft.sqlserver.jdbc.SQLServerDataSource; import com.microsoft.sqlserver.jdbc.TestResource; import com.microsoft.sqlserver.jdbc.TestUtils; +import com.microsoft.sqlserver.jdbc.SQLServerDataTable; +import com.microsoft.sqlserver.testframework.PrepUtil; import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; import com.microsoft.sqlserver.testframework.AbstractTest; import com.microsoft.sqlserver.testframework.Constants; +import javax.sql.DataSource; + /** * Test CallableStatement @@ -65,6 +70,14 @@ public class CallableStatementTest extends AbstractTest { .escapeIdentifier(RandomUtil.getIdentifier("manyParam_Procedure")); private static String manyParamUserDefinedType = AbstractSQLGenerator .escapeIdentifier(RandomUtil.getIdentifier("manyParam_definedType")); + private static String zeroParamSproc = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("zeroParamSproc")); + private static String outOfOrderSproc = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("outOfOrderSproc")); + private static String byParamNameSproc = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("byParamNameSproc")); + private static String userDefinedFunction = AbstractSQLGenerator + .escapeIdentifier(RandomUtil.getIdentifier("userDefinedFunction")); /** * Setup before test @@ -82,6 +95,10 @@ public static void setupTest() throws Exception { TestUtils.dropProcedureIfExists(inputParamsProcedureName, stmt); TestUtils.dropProcedureIfExists(getObjectLocalDateTimeProcedureName, stmt); TestUtils.dropProcedureIfExists(getObjectOffsetDateTimeProcedureName, stmt); + TestUtils.dropProcedureIfExists(zeroParamSproc, stmt); + TestUtils.dropProcedureIfExists(outOfOrderSproc, stmt); + TestUtils.dropProcedureIfExists(byParamNameSproc, stmt); + TestUtils.dropFunctionIfExists(userDefinedFunction, stmt); TestUtils.dropUserDefinedTypeIfExists(manyParamUserDefinedType, stmt); TestUtils.dropProcedureIfExists(manyParamProc, stmt); TestUtils.dropTableIfExists(manyParamsTable, stmt); @@ -95,6 +112,21 @@ public static void setupTest() throws Exception { createTableManyParams(); createProcedureManyParams(); createGetObjectOffsetDateTimeProcedure(stmt); + createProcedureZeroParams(); + createOutOfOrderSproc(); + createByParamNameSproc(); + createUserDefinedFunction(); + } + } + + @Test + public void testCallableStatementClosedConnection() { + try (SQLServerCallableStatement stmt = (SQLServerCallableStatement) connection.prepareCall("sproc")) { + stmt.close(); // Prematurely close the statement, which causes inOutParams to be null. + stmt.setStructured("myParam", "myTvp", (SQLServerDataTable) null); + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + assertEquals(TestResource.getResource("R_statementClosed"), e.getMessage()); } } @@ -332,7 +364,6 @@ public void inputParamsTest() throws SQLException { cs.setString("@whatever", "test"); fail(TestResource.getResource("R_shouldThrowException")); } catch (SQLException sse) { - MessageFormat form = new MessageFormat(TestResource.getResource("R_parameterNotDefined")); Object[] msgArgs = {"@whatever"}; @@ -342,6 +373,551 @@ public void inputParamsTest() throws SQLException { } } + @Test + public void testZeroParamSproc() throws SQLException { + String call = "{? = CALL " + zeroParamSproc + "}"; + + try (CallableStatement cs = connection.prepareCall(call)) { + cs.registerOutParameter(1, Types.INTEGER); + cs.execute(); + assertEquals(1, cs.getInt(1)); + } + } + + @Test + public void testSprocCastingError() { + String call = "{? = CALL " + zeroParamSproc + "}"; + + try (CallableStatement cs = connection.prepareCall(call)) { + cs.registerOutParameter(1, Types.BINARY); + cs.execute(); // Should not be able to get return value as bytes + cs.getBytes(1); + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + assertTrue(e.getMessage().contains("cannot be cast to")); + } + } + + @Test + public void testNonOrderedRegisteringAndSettingOfParams() throws SQLException { + String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; + + try (CallableStatement cstmt = connection.prepareCall(call)) { + int scale = 6; + Double obj1 = 2015.0123; + Double obj2 = 2015.012345; + Integer obj3 = -3; + Float obj4 = 2015.04f; + Integer obj5 = 3; + String obj6 = "foo"; + String obj7 = "bar"; + Long obj8 = 2015L; + + cstmt.setObject("i5", obj5, Types.CHAR); + cstmt.setObject("i6", obj6, Types.VARCHAR); + cstmt.setObject("i7", obj7, Types.CHAR); + cstmt.setObject("i8", obj8, Types.SMALLINT); + + cstmt.setObject(1, obj1, Types.NUMERIC); + cstmt.setObject(2, obj2, Types.NUMERIC, scale); + cstmt.setObject(3, obj3, Types.INTEGER); + cstmt.setObject(4, obj4, Types.FLOAT); + + cstmt.registerOutParameter(9, Types.NUMERIC); + cstmt.registerOutParameter("o2", Types.NUMERIC, scale); + cstmt.registerOutParameter("o3", Types.INTEGER); + cstmt.registerOutParameter("o4", Types.FLOAT); + + cstmt.registerOutParameter(13, Types.CHAR); + cstmt.registerOutParameter(14, Types.VARCHAR); + cstmt.registerOutParameter(15, Types.CHAR); + cstmt.registerOutParameter(16, Types.SMALLINT); + cstmt.execute(); + } + } + + @Test + public void testNamedParametersUseFlexibleCallableStatementFalse() throws SQLException { + String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + + // useFlexibleCallableStatement=false and using all named parameters + try (Connection conn = DriverManager.getConnection(connectionString)) { + try (CallableStatement cstmt = conn.prepareCall(call)) { + int scale = 6; + double obj1 = 2015.0123; + double obj2 = 2015.012345; + int obj3 = -3; + float obj4 = 2015.04f; + int obj5 = 3; + String obj6 = "foo"; + String obj7 = "b"; + long obj8 = 2015L; + + cstmt.setObject("i1", obj1, Types.NUMERIC); + cstmt.setObject("i2", obj2, Types.NUMERIC, scale); + cstmt.setObject("i3", obj3, Types.INTEGER); + cstmt.setObject("i4", obj4, Types.FLOAT); + + cstmt.setObject("i5", obj5, Types.CHAR); + cstmt.setObject("i6", obj6, Types.VARCHAR); + cstmt.setObject("i7", obj7, Types.CHAR); + cstmt.setObject("i8", obj8, Types.SMALLINT); + + cstmt.registerOutParameter("o1", Types.NUMERIC); + cstmt.registerOutParameter("o2", Types.NUMERIC, scale); + cstmt.registerOutParameter("o3", Types.INTEGER); + cstmt.registerOutParameter("o4", Types.FLOAT); + + cstmt.registerOutParameter("o5", Types.CHAR); + cstmt.registerOutParameter("o6", Types.VARCHAR); + cstmt.registerOutParameter("o7", Types.CHAR); + cstmt.registerOutParameter("o8", Types.SMALLINT); + cstmt.execute(); + + assertEquals(obj1, cstmt.getDouble("o1")); + assertEquals(obj2, cstmt.getDouble("o2")); + assertEquals(obj3, cstmt.getInt("o3")); + assertEquals(obj4, cstmt.getFloat("o4")); + assertEquals(obj5, cstmt.getInt("o5")); + assertEquals(obj6, cstmt.getString("o6")); + assertEquals(obj7, cstmt.getString("o7")); + assertEquals(obj8, cstmt.getLong("o8")); + } + } + } + + @Test + public void testNamedParametersAcquireOutputParamValuesByIndexUseFlexibleCallableStatementFalse() throws SQLException { + String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + + // useFlexibleCallableStatement=false + // Setting parameters by name + // Registering output parameters by name + // Acquiring output parameter values by index + try (Connection conn = DriverManager.getConnection(connectionString)) { + try (CallableStatement cstmt = conn.prepareCall(call)) { + int scale = 6; + double obj1 = 2015.0123; + double obj2 = 2015.012345; + int obj3 = -3; + float obj4 = 2015.04f; + int obj5 = 3; + String obj6 = "foo"; + String obj7 = "b"; + long obj8 = 2015L; + + cstmt.setObject("i1", obj1, Types.NUMERIC); + cstmt.setObject("i2", obj2, Types.NUMERIC, scale); + cstmt.setObject("i3", obj3, Types.INTEGER); + cstmt.setObject("i4", obj4, Types.FLOAT); + + cstmt.setObject("i5", obj5, Types.CHAR); + cstmt.setObject("i6", obj6, Types.VARCHAR); + cstmt.setObject("i7", obj7, Types.CHAR); + cstmt.setObject("i8", obj8, Types.SMALLINT); + + cstmt.registerOutParameter("o1", Types.NUMERIC); + cstmt.registerOutParameter("o2", Types.NUMERIC, scale); + cstmt.registerOutParameter("o3", Types.INTEGER); + cstmt.registerOutParameter("o4", Types.FLOAT); + + cstmt.registerOutParameter("o5", Types.CHAR); + cstmt.registerOutParameter("o6", Types.VARCHAR); + cstmt.registerOutParameter("o7", Types.CHAR); + cstmt.registerOutParameter("o8", Types.SMALLINT); + cstmt.execute(); + + assertEquals(obj1, cstmt.getDouble(9)); + assertEquals(obj2, cstmt.getDouble(10)); + assertEquals(obj3, cstmt.getInt(11)); + assertEquals(obj4, cstmt.getFloat(12)); + assertEquals(obj5, cstmt.getInt(13)); + assertEquals(obj6, cstmt.getString(14)); + assertEquals(obj7, cstmt.getString(15)); + assertEquals(obj8, cstmt.getLong(16)); + } + } + } + + @Test + public void testNamedParametersAndOutParamByIndexUseFlexibleCallableStatementFalse() throws SQLException { + String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + + // useFlexibleCallableStatement=false + // Setting parameters by name + // Registering output params by index + // Acquiring output param values by index + try (Connection conn = DriverManager.getConnection(connectionString)) { + try (CallableStatement cstmt = conn.prepareCall(call)) { + int scale = 6; + double obj1 = 2015.0123; + double obj2 = 2015.012345; + int obj3 = -3; + float obj4 = 2015.04f; + int obj5 = 3; + String obj6 = "foo"; + String obj7 = "b"; + long obj8 = 2015L; + + cstmt.setObject("i1", obj1, Types.NUMERIC); + cstmt.setObject("i2", obj2, Types.NUMERIC, scale); + cstmt.setObject("i3", obj3, Types.INTEGER); + cstmt.setObject("i4", obj4, Types.FLOAT); + + cstmt.setObject("i5", obj5, Types.CHAR); + cstmt.setObject("i6", obj6, Types.VARCHAR); + cstmt.setObject("i7", obj7, Types.CHAR); + cstmt.setObject("i8", obj8, Types.SMALLINT); + + cstmt.registerOutParameter(9, Types.NUMERIC); + cstmt.registerOutParameter(10, Types.NUMERIC, scale); + cstmt.registerOutParameter(11, Types.INTEGER); + cstmt.registerOutParameter(12, Types.FLOAT); + + cstmt.registerOutParameter(13, Types.CHAR); + cstmt.registerOutParameter(14, Types.VARCHAR); + cstmt.registerOutParameter(15, Types.CHAR); + cstmt.registerOutParameter(16, Types.SMALLINT); + cstmt.execute(); + + assertEquals(obj1, cstmt.getDouble(9)); + assertEquals(obj2, cstmt.getDouble(10)); + assertEquals(obj3, cstmt.getInt(11)); + assertEquals(obj4, cstmt.getFloat(12)); + assertEquals(obj5, cstmt.getInt(13)); + assertEquals(obj6, cstmt.getString(14)); + assertEquals(obj7, cstmt.getString(15)); + assertEquals(obj8, cstmt.getLong(16)); + } + } + } + + @Test + public void testNamedParameterOutputParameterErrorFlexibleCallableStatementFalse() throws SQLException { + String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + + // useFlexibleCallableStatement=false + // Setting parameters by name + // Registering output params by index + // Acquiring output param value 'o1' by name + try (Connection conn = DriverManager.getConnection(connectionString)) { + try (CallableStatement cstmt = conn.prepareCall(call)) { + int scale = 6; + double obj1 = 2015.0123; + double obj2 = 2015.012345; + int obj3 = -3; + float obj4 = 2015.04f; + int obj5 = 3; + String obj6 = "foo"; + String obj7 = "b"; + long obj8 = 2015L; + + cstmt.setObject("i1", obj1, Types.NUMERIC); + cstmt.setObject("i2", obj2, Types.NUMERIC, scale); + cstmt.setObject("i3", obj3, Types.INTEGER); + cstmt.setObject("i4", obj4, Types.FLOAT); + + cstmt.setObject("i5", obj5, Types.CHAR); + cstmt.setObject("i6", obj6, Types.VARCHAR); + cstmt.setObject("i7", obj7, Types.CHAR); + cstmt.setObject("i8", obj8, Types.SMALLINT); + + cstmt.registerOutParameter(9, Types.NUMERIC); + cstmt.registerOutParameter(10, Types.NUMERIC, scale); + cstmt.registerOutParameter(11, Types.INTEGER); + cstmt.registerOutParameter(12, Types.FLOAT); + + cstmt.registerOutParameter(13, Types.CHAR); + cstmt.registerOutParameter(14, Types.VARCHAR); + cstmt.registerOutParameter(15, Types.CHAR); + cstmt.registerOutParameter(16, Types.SMALLINT); + cstmt.execute(); + + assertEquals(obj1, cstmt.getDouble("o1")); + assertEquals(obj2, cstmt.getDouble(10)); + assertEquals(obj3, cstmt.getInt(11)); + assertEquals(obj4, cstmt.getFloat(12)); + assertEquals(obj5, cstmt.getInt(13)); + assertEquals(obj6, cstmt.getString(14)); + assertEquals(obj7, cstmt.getString(15)); + assertEquals(obj8, cstmt.getLong(16)); + + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + assertEquals(TestResource.getResource("R_unknownOutputParameter"), e.getMessage()); + } + } + } + + @Test + public void testNamedParametersAndByIndexErrorUseFlexibleCallableStatementFalse() throws SQLException { + String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + + // useFlexibleCallableStatement=false + // Using majority named parameters and setting parameter by index + try (Connection conn = DriverManager.getConnection(connectionString)) { + try (CallableStatement cstmt = conn.prepareCall(call)) { + int scale = 6; + double obj1 = 2015.0123; + double obj2 = 2015.012345; + int obj3 = -3; + float obj4 = 2015.04f; + int obj5 = 3; + String obj6 = "foo"; + String obj7 = "b"; + long obj8 = 2015L; + + cstmt.setObject("i1", obj1, Types.NUMERIC); + cstmt.setObject("i2", obj2, Types.NUMERIC, scale); + cstmt.setObject("i3", obj3, Types.INTEGER); + cstmt.setObject("i4", obj4, Types.FLOAT); + + cstmt.setObject("i5", obj5, Types.CHAR); + cstmt.setObject("i6", obj6, Types.VARCHAR); + cstmt.setObject("i7", obj7, Types.CHAR); + cstmt.setObject(8, obj8, Types.SMALLINT); + + cstmt.registerOutParameter("o1", Types.NUMERIC); + cstmt.registerOutParameter("o2", Types.NUMERIC, scale); + cstmt.registerOutParameter("o3", Types.INTEGER); + cstmt.registerOutParameter("o4", Types.FLOAT); + + cstmt.registerOutParameter("o5", Types.CHAR); + cstmt.registerOutParameter("o6", Types.VARCHAR); + cstmt.registerOutParameter("o7", Types.CHAR); + cstmt.registerOutParameter("o8", Types.SMALLINT); + cstmt.execute(); + + assertEquals(obj1, cstmt.getDouble("o1")); + assertEquals(obj2, cstmt.getDouble("o2")); + assertEquals(obj3, cstmt.getInt("o3")); + assertEquals(obj4, cstmt.getFloat("o4")); + assertEquals(obj5, cstmt.getInt("o5")); + assertEquals(obj6, cstmt.getString("o6")); + assertEquals(obj7, cstmt.getString("o7")); + assertEquals(obj8, cstmt.getLong("o8")); + + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + assertEquals(TestResource.getResource("R_noNamedAndIndexedParameters"), e.getMessage()); + } + } + } + + @Test + public void testIndexedParametersUseFlexibleCallableStatementFalse() throws SQLException { + String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + + // useFlexibleCallableStatement=false and using all index parameters + try (Connection conn = DriverManager.getConnection(connectionString)) { + try (CallableStatement cstmt = conn.prepareCall(call)) { + int scale = 6; + double obj1 = 2015.0123; + double obj2 = 2015.012345; + int obj3 = -3; + float obj4 = 2015.04f; + int obj5 = 3; + String obj6 = "foo"; + String obj7 = "b"; + long obj8 = 2015L; + + cstmt.setObject(1, obj1, Types.NUMERIC); + cstmt.setObject(2, obj2, Types.NUMERIC, scale); + cstmt.setObject(3, obj3, Types.INTEGER); + cstmt.setObject(4, obj4, Types.FLOAT); + + cstmt.setObject(5, obj5, Types.CHAR); + cstmt.setObject(6, obj6, Types.VARCHAR); + cstmt.setObject(7, obj7, Types.CHAR); + cstmt.setObject(8, obj8, Types.SMALLINT); + + cstmt.registerOutParameter(9, Types.NUMERIC); + cstmt.registerOutParameter(10, Types.NUMERIC, scale); + cstmt.registerOutParameter(11, Types.INTEGER); + cstmt.registerOutParameter(12, Types.FLOAT); + + cstmt.registerOutParameter(13, Types.CHAR); + cstmt.registerOutParameter(14, Types.VARCHAR); + cstmt.registerOutParameter(15, Types.CHAR); + cstmt.registerOutParameter(16, Types.SMALLINT); + cstmt.execute(); + + assertEquals(obj1, cstmt.getDouble(9)); + assertEquals(obj2, cstmt.getDouble(10)); + assertEquals(obj3, cstmt.getInt(11)); + assertEquals(obj4, cstmt.getFloat(12)); + assertEquals(obj5, cstmt.getInt(13)); + assertEquals(obj6, cstmt.getString(14)); + assertEquals(obj7, cstmt.getString(15)); + assertEquals(obj8, cstmt.getLong(16)); + } + } + } + + @Test + public void testIndexedParametersAcquireOutputValueByNameErrorUseFlexibleCallableStatementFalse() throws SQLException { + String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + + try (Connection conn = DriverManager.getConnection(connectionString)) { + try (CallableStatement cstmt = conn.prepareCall(call)) { + int scale = 6; + double obj1 = 2015.0123; + double obj2 = 2015.012345; + int obj3 = -3; + float obj4 = 2015.04f; + int obj5 = 3; + String obj6 = "foo"; + String obj7 = "b"; + long obj8 = 2015L; + + cstmt.setObject(1, obj1, Types.NUMERIC); + cstmt.setObject(2, obj2, Types.NUMERIC, scale); + cstmt.setObject(3, obj3, Types.INTEGER); + cstmt.setObject(4, obj4, Types.FLOAT); + + cstmt.setObject(5, obj5, Types.CHAR); + cstmt.setObject(6, obj6, Types.VARCHAR); + cstmt.setObject(7, obj7, Types.CHAR); + cstmt.setObject(8, obj8, Types.SMALLINT); + + cstmt.registerOutParameter(9, Types.NUMERIC); + cstmt.registerOutParameter(10, Types.NUMERIC, scale); + cstmt.registerOutParameter(11, Types.INTEGER); + cstmt.registerOutParameter(12, Types.FLOAT); + + cstmt.registerOutParameter(13, Types.CHAR); + cstmt.registerOutParameter(14, Types.VARCHAR); + cstmt.registerOutParameter(15, Types.CHAR); + cstmt.registerOutParameter(16, Types.SMALLINT); + cstmt.execute(); + + assertEquals(obj1, cstmt.getDouble(9)); + assertEquals(obj2, cstmt.getDouble(10)); + assertEquals(obj3, cstmt.getInt(11)); + assertEquals(obj4, cstmt.getFloat(12)); + assertEquals(obj5, cstmt.getInt(13)); + assertEquals(obj6, cstmt.getString(14)); + assertEquals(obj7, cstmt.getString(15)); + assertEquals(obj8, cstmt.getLong("o8")); + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + assertEquals(TestResource.getResource("R_noNamedAndIndexedParameters"), e.getMessage()); + } + } + } + + @Test + public void testIndexedParametersRegisterOutputParamByNameUseFlexibleCallableStatementFalse() throws SQLException { + String call = "{CALL " + outOfOrderSproc + " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"; + String connectionString = TestUtils.addOrOverrideProperty(getConnectionString(), "useFlexibleCallableStatements", "false"); + + try (Connection conn = DriverManager.getConnection(connectionString)) { + try (CallableStatement cstmt = conn.prepareCall(call)) { + int scale = 6; + double obj1 = 2015.0123; + double obj2 = 2015.012345; + int obj3 = -3; + float obj4 = 2015.04f; + int obj5 = 3; + String obj6 = "foo"; + String obj7 = "b"; + long obj8 = 2015L; + + cstmt.setObject(1, obj1, Types.NUMERIC); + cstmt.setObject(2, obj2, Types.NUMERIC, scale); + cstmt.setObject(3, obj3, Types.INTEGER); + cstmt.setObject(4, obj4, Types.FLOAT); + + cstmt.setObject(5, obj5, Types.CHAR); + cstmt.setObject(6, obj6, Types.VARCHAR); + cstmt.setObject(7, obj7, Types.CHAR); + cstmt.setObject(8, obj8, Types.SMALLINT); + + cstmt.registerOutParameter("o1", Types.NUMERIC); + cstmt.registerOutParameter(10, Types.NUMERIC, scale); + cstmt.registerOutParameter(11, Types.INTEGER); + cstmt.registerOutParameter(12, Types.FLOAT); + + cstmt.registerOutParameter(13, Types.CHAR); + cstmt.registerOutParameter(14, Types.VARCHAR); + cstmt.registerOutParameter(15, Types.CHAR); + cstmt.registerOutParameter(16, Types.SMALLINT); + cstmt.execute(); + + assertEquals(obj1, cstmt.getDouble(9)); + assertEquals(obj2, cstmt.getDouble(10)); + assertEquals(obj3, cstmt.getInt(11)); + assertEquals(obj4, cstmt.getFloat(12)); + assertEquals(obj5, cstmt.getInt(13)); + assertEquals(obj6, cstmt.getString(14)); + assertEquals(obj7, cstmt.getString(15)); + assertEquals(obj8, cstmt.getLong(16)); + + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + assertEquals(TestResource.getResource("R_noNamedAndIndexedParameters"), e.getMessage()); + } + } + } + + @Test + public void testExecutingUserDefinedFunctionDirectly() throws SQLException { + String call = "{? = CALL " + userDefinedFunction + " (?,?,?,?,?,?)}"; + + try (CallableStatement cstmt = connection.prepareCall(call)) { + cstmt.setObject(2, "param"); + cstmt.setObject(3, "param"); + cstmt.setObject(4, "param"); + cstmt.setObject(5, "param"); + cstmt.setObject(6, "param"); + cstmt.setObject(7, "param"); + cstmt.registerOutParameter(1, Types.VARCHAR); + cstmt.execute(); + assertEquals("foobar", cstmt.getString(1)); + + // Re-execute again to test skipping of unread return values on statement close. + // If it fails, TDS errors will be thrown. + cstmt.execute(); + } + } + + @Test + public void testRegisteringOutputByIndexandAcquiringOutputParamByName() throws SQLException { + String call = "{CALL " + byParamNameSproc + " (?,?)}"; + + // Param names are p1 and p2 + try (CallableStatement cstmt = connection.prepareCall(call)) { + cstmt.setString(1, "foobar"); + cstmt.registerOutParameter(2, Types.NVARCHAR); + cstmt.execute(); + + assertEquals("foobar", cstmt.getString("p2")); + } + + try (CallableStatement cstmt = connection.prepareCall(call)) { + cstmt.setString("p1", "foobar"); + cstmt.registerOutParameter("p2", Types.NVARCHAR); + cstmt.execute(); + + assertEquals("foobar", cstmt.getString("p2")); + } + + try (CallableStatement cstmt = connection.prepareCall(call)) { + cstmt.setString("p1", "foobar"); + cstmt.registerOutParameter("p2", Types.NVARCHAR); + cstmt.execute(); + + assertEquals("foobar", cstmt.getString(2)); + } + } + /** * Cleanup after test * @@ -357,6 +933,10 @@ public static void cleanup() throws SQLException { TestUtils.dropProcedureIfExists(inputParamsProcedureName, stmt); TestUtils.dropProcedureIfExists(getObjectLocalDateTimeProcedureName, stmt); TestUtils.dropProcedureIfExists(getObjectOffsetDateTimeProcedureName, stmt); + TestUtils.dropProcedureIfExists(zeroParamSproc, stmt); + TestUtils.dropProcedureIfExists(outOfOrderSproc, stmt); + TestUtils.dropProcedureIfExists(byParamNameSproc, stmt); + TestUtils.dropFunctionIfExists(userDefinedFunction, stmt); } } @@ -425,4 +1005,41 @@ private static void createUserDefinedType() throws SQLException { stmt.executeUpdate(TVPCreateCmd); } } + + private static void createProcedureZeroParams() throws SQLException { + String sql = "CREATE PROCEDURE " + zeroParamSproc + " AS RETURN 1"; + try (Statement stmt = connection.createStatement()) { + stmt.execute(sql); + } + } + + private static void createOutOfOrderSproc() throws SQLException { + String sql = "CREATE PROCEDURE " + outOfOrderSproc + " @i1 NUMERIC(16,10)," + " @i2 NUMERIC(16,6)," + + " @i3 INT," + " @i4 REAL," + " @i5 CHAR," + " @i6 VARCHAR(6)," + " @i7 CHAR," + " @i8 SMALLINT, " + + " @o1 NUMERIC(16,10) OUTPUT," + " @o2 NUMERIC(16,6) OUTPUT," + " @o3 INT OUTPUT," + + " @o4 REAL OUTPUT," + " @o5 CHAR OUTPUT," + " @o6 VARCHAR(6) OUTPUT," + " @o7 CHAR OUTPUT," + + " @o8 SMALLINT OUTPUT" + " as begin " + " set @o1=@i1;" + " set @o2=@i2;" + " set @o3=@i3;" + + " set @o4=@i4;" + " set @o5=@i5;" + " set @o6=@i6;" + " set @o7=@i7;" + " set @o8=@i8;" + " end"; + try (Statement stmt = connection.createStatement()) { + stmt.execute(sql); + } + } + + private static void createByParamNameSproc() throws SQLException { + String sql = "CREATE PROCEDURE " + byParamNameSproc + + " (@p1 nvarchar(30), @p2 nvarchar(30) output) AS BEGIN SELECT @p2 = @p1 END;"; + try (Statement stmt = connection.createStatement()) { + stmt.execute(sql); + } + } + + private static void createUserDefinedFunction() throws SQLException { + String sql = "CREATE FUNCTION " + userDefinedFunction + + " (@p0 char(20), @p1 varchar(50), @p2 varchar(max), @p3 nchar(30), @p4 nvarchar(60), @p5 nvarchar(max)) " + + "RETURNS varchar(50) AS BEGIN " + "DECLARE @ret varchar(50); " + "SELECT @ret = 'foobar'; " + + " RETURN @ret; end;"; + try (Statement stmt = connection.createStatement()) { + stmt.execute(sql); + } + } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java index bb524b67f..e02be6e4c 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java @@ -509,6 +509,8 @@ private List getVerifiedMethodNames() { verifiedMethodNames.add("setMsiTokenCacheTtl"); verifiedMethodNames.add("getAccessTokenCallbackClass"); verifiedMethodNames.add("setAccessTokenCallbackClass"); + verifiedMethodNames.add("getUseFlexibleCallableStatements"); + verifiedMethodNames.add("setUseFlexibleCallableStatements"); verifiedMethodNames.add("getCalcBigDecimalScale"); verifiedMethodNames.add("setCalcBigDecimalScale"); return verifiedMethodNames; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DateAndTimeTypeTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DateAndTimeTypeTest.java index 99dbe64dc..e8714b1c0 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DateAndTimeTypeTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DateAndTimeTypeTest.java @@ -10,6 +10,8 @@ import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; +import java.sql.CallableStatement; +import java.sql.Types; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; @@ -49,6 +51,8 @@ public class DateAndTimeTypeTest extends AbstractTest { private static final String timeTVP = RandomUtil.getIdentifier("timeTVP"); private static final String timestampTVP = RandomUtil.getIdentifier("timestampTVP"); private static final String tableName = RandomUtil.getIdentifier("DataTypesTable"); + private static final String datetimeTable = RandomUtil.getIdentifier("DateTimeTable"); + private static final String datetimeSproc = RandomUtil.getIdentifier("dateTimeSproc"); private static final String primaryKeyConstraintName = "pk_" + tableName; /** @@ -205,21 +209,21 @@ public static void setupTests() throws Exception { * Test to make sure that a Timestamp is treated as a datetime object. */ @Test - public void testSendTimestampAsDatetime() throws Exception { + public void testSendTimestampAsDatetime() throws Exception { String expected = "2010-02-01T23:59:59.997"; String actual = null; String query = "SELECT CONVERT(VARCHAR(40), ?, 126) as [value]"; - try (SQLServerConnection conn = PrepUtil.getConnection(connectionString + ";datetimeParameterType=datetime"); - PreparedStatement stmt = conn.prepareStatement(query)) { + try (SQLServerConnection conn = PrepUtil.getConnection(connectionString + ";datetimeParameterType=datetime"); + PreparedStatement stmt = conn.prepareStatement(query)) { Timestamp ts = Timestamp.valueOf("2010-02-01 23:59:59.996"); // if cast to a datetime, 996ms is rounded up to 997ms /* - * send the timestamp to the server using the TIME SQL type rather than TIMESTAMP. The driver will - * strip the date portion and, because sendTimeAsDatetime=true, round the resulting time value to - * midnight because it should be sending a DATETIME which has only 1/300s accuracy - */ + * send the timestamp to the server using the TIME SQL type rather than TIMESTAMP. The driver will + * strip the date portion and, because sendTimeAsDatetime=true, round the resulting time value to + * midnight because it should be sending a DATETIME which has only 1/300s accuracy + */ stmt.setObject(1, ts, java.sql.Types.TIMESTAMP); ResultSet rs = stmt.executeQuery(); @@ -236,21 +240,21 @@ public void testSendTimestampAsDatetime() throws Exception { * Test to make sure that a Timestamp is treated as a datetime2 object. */ @Test - public void testSendTimestampAsDatetime2() throws Exception { + public void testSendTimestampAsDatetime2() throws Exception { String expected = "2010-02-02T23:59:59.1234567"; String actual = null; String query = "SELECT CONVERT(VARCHAR(40), ?, 126) as [value]"; - try (SQLServerConnection conn = PrepUtil.getConnection(connectionString + ";datetimeParameterType=datetime2"); - PreparedStatement stmt = conn.prepareStatement(query)) { + try (SQLServerConnection conn = PrepUtil.getConnection(connectionString + ";datetimeParameterType=datetime2"); + PreparedStatement stmt = conn.prepareStatement(query)) { Timestamp ts = Timestamp.valueOf("2010-02-02 23:59:59.1234567"); /* - * send the timestamp to the server using the TIME SQL type rather than TIMESTAMP. The driver will - * strip the date portion and, because sendTimeAsDatetime=true, round the resulting time value to - * midnight because it should be sending a DATETIME which has only 1/300s accuracy - */ + * send the timestamp to the server using the TIME SQL type rather than TIMESTAMP. The driver will + * strip the date portion and, because sendTimeAsDatetime=true, round the resulting time value to + * midnight because it should be sending a DATETIME which has only 1/300s accuracy + */ stmt.setObject(1, ts, java.sql.Types.TIMESTAMP); ResultSet rs = stmt.executeQuery(); @@ -267,22 +271,22 @@ public void testSendTimestampAsDatetime2() throws Exception { * Test to make sure that a Timestamp is treated as a datetime2 object. */ @Test - public void testSendTimestampAsDatetimeoffset() throws Exception { + public void testSendTimestampAsDatetimeoffset() throws Exception { String expected = "2010-02-03T23:59:59.7654321Z"; String actual = null; String query = "SELECT CONVERT(VARCHAR(40), ?, 127) as [value]"; - - try (SQLServerConnection conn = PrepUtil.getConnection(connectionString + ";datetimeParameterType=datetimeoffset"); - PreparedStatement stmt = conn.prepareStatement(query)) { + try (SQLServerConnection conn = PrepUtil + .getConnection(connectionString + ";datetimeParameterType=datetimeoffset"); + PreparedStatement stmt = conn.prepareStatement(query)) { Timestamp ts = Timestamp.valueOf("2010-02-03 23:59:59.7654321"); /* - * send the timestamp to the server using the TIME SQL type rather than TIMESTAMP. The driver will - * strip the date portion and, because sendTimeAsDatetime=true, round the resulting time value to - * midnight because it should be sending a DATETIME which has only 1/300s accuracy - */ + * send the timestamp to the server using the TIME SQL type rather than TIMESTAMP. The driver will + * strip the date portion and, because sendTimeAsDatetime=true, round the resulting time value to + * midnight because it should be sending a DATETIME which has only 1/300s accuracy + */ stmt.setObject(1, ts, java.sql.Types.TIMESTAMP); ResultSet rs = stmt.executeQuery(); @@ -295,6 +299,85 @@ public void testSendTimestampAsDatetimeoffset() throws Exception { assertEquals(expected, actual); } + @Test + public void testCstmtRegisterDatetimeOutParameterDateTimeParameterTypeDatetime() throws Exception { + String expected = "3160-08-17 19:09:06.937"; + String actual = null; + String query = "{CALL " + AbstractSQLGenerator.escapeIdentifier(datetimeSproc) + "(?)}"; + + // The following should be the T-SQL executed. Since datetimeParameterType=dateTime, the registered parameter type + // should be 'datetime' eg. declare @p1 datetime as shown below. The expected fractional seconds should be 3. + // + // declare @p1 datetime + // set @p1='3160-08-17 19:09:06.937' + // exec sproc @p1 output + // select @p1 + try (SQLServerConnection conn = PrepUtil.getConnection(connectionString + ";datetimeParameterType=datetime"); + CallableStatement stmt = conn.prepareCall(query)) { + + stmt.registerOutParameter(1, microsoft.sql.Types.DATETIME); + stmt.execute(); + + actual = stmt.getString(1); + System.out.println(actual); + } + + assertEquals(expected, actual.toString()); + } + + @Test + public void testCstmtRegisterDatetimeOutParameterDateTimeParameterTypeDatetime2() throws Exception { + String expected = "3160-08-17 19:09:06.937"; + String actual = null; + String query = "{CALL " + AbstractSQLGenerator.escapeIdentifier(datetimeSproc) + "(?)}"; + + // The following should be the T-SQL executed. Since datetimeParameterType=dateTime2, the registered param type + // should be 'datetime2(3)' eg. declare @p1 datetime2(3) as shown below. The expected fractional seconds + // should be 7 in the T-SQL, but in the expected actual output it's rounded so fractional seconds should be 3. + // + // declare @p1 datetime2(3) + // set @p1='set @p1='3160-08-17 19:09:06.9370000'' + // exec sproc @p1 output + // select @p1 + try (SQLServerConnection conn = PrepUtil.getConnection(connectionString + ";datetimeParameterType=datetime2"); + CallableStatement stmt = conn.prepareCall(query)) { + + stmt.registerOutParameter(1, microsoft.sql.Types.DATETIME); + stmt.execute(); + + actual = stmt.getString(1); + System.out.println(actual); + } + + assertEquals(expected, actual.toString()); + } + + @Test + public void testCstmtRegisterTimestampOutParameter() throws Exception { + String expected = "3160-08-17 19:09:06.9366667"; + String actual = null; + String query = "{CALL " + AbstractSQLGenerator.escapeIdentifier(datetimeSproc) + "(?)}"; + + // The following should be the T-SQL executed. Default driver behaviour for Timestamp SQL types is to register + // the param in the RPC as datetime2(7). So fractional seconds should be 7. + // + // declare @p1 datetime2(7) + // set @p1='3160-08-17 19:09:06.9366667' + // exec sproc @p1 output + // select @p1 + try (SQLServerConnection conn = PrepUtil.getConnection(connectionString); + CallableStatement stmt = conn.prepareCall(query)) { + + stmt.registerOutParameter(1, Types.TIMESTAMP); + stmt.execute(); + + actual = stmt.getString(1); + System.out.println(actual); + } + + assertEquals(expected, actual.toString()); + } + @BeforeEach public void testSetup() throws TestAbortedException, Exception { // To get TIME & setTime working on Servers >= 2008, we must add 'sendTimeAsDatetime=false' @@ -302,10 +385,19 @@ public void testSetup() throws TestAbortedException, Exception { try (Connection connection = PrepUtil.getConnection(connectionString + ";sendTimeAsDatetime=false"); Statement stmt = (SQLServerStatement) connection.createStatement()) { TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); + TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(datetimeTable), stmt); + TestUtils.dropProcedureIfExists(AbstractSQLGenerator.escapeIdentifier(datetimeSproc), stmt); String sql1 = "create table " + AbstractSQLGenerator.escapeIdentifier(tableName) + " (id integer not null, my_date date, my_time time, my_timestamp datetime2 constraint " + AbstractSQLGenerator.escapeIdentifier(primaryKeyConstraintName) + " primary key (id))"; + String sql2 = "create table " + AbstractSQLGenerator.escapeIdentifier(datetimeTable) + + " (c1 datetime2 NULL)"; + String sql3 = "create procedure " + AbstractSQLGenerator.escapeIdentifier(datetimeSproc) + + " (@p1 datetime2 output) as select top 1 @p1=c1 from " + + AbstractSQLGenerator.escapeIdentifier(datetimeTable); stmt.execute(sql1); + stmt.execute(sql2); + stmt.execute(sql3); // add one sample data String sPrepStmt = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableName) @@ -321,6 +413,9 @@ public void testSetup() throws TestAbortedException, Exception { createTVPs(timeTVP, "time"); createTVPs(timestampTVP, "datetime2"); } + + stmt.execute("insert into " + AbstractSQLGenerator.escapeIdentifier(datetimeTable) + + " values('3160-08-17 19:09:06.9366667')"); } } @@ -328,6 +423,8 @@ public void testSetup() throws TestAbortedException, Exception { public static void terminateVariation() throws SQLException { try (Statement stmt = connection.createStatement()) { TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt); + TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(datetimeTable), stmt); + TestUtils.dropProcedureIfExists(AbstractSQLGenerator.escapeIdentifier(datetimeSproc), stmt); } } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java index deb7abc02..f6215479f 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java @@ -1661,7 +1661,7 @@ public void testMathBigDecimalDivision() throws SQLException { */ @Test @Tag(Constants.xAzureSQLDW) - public void testResultSetErrors() throws Exception { + public void testRetrievingRegisteredOutParamWhenResultSetDoesNotExists() throws Exception { try (Connection con = getConnection(); Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)) { @@ -1678,7 +1678,10 @@ public void testResultSetErrors() throws Exception { try (ResultSet rs = cstmt.executeQuery()) {} catch (Exception ex) {} ; - assertEquals(null, cstmt.getString(2), TestResource.getResource("R_valueNotMatch")); + try { + cstmt.getString(2); + fail(TestResource.getResource("R_expectedExceptionNotThrown")); + } catch (Exception e) {} } } }