From 6789eb6fc86fe5b31a1ffd769f87065a616de2c2 Mon Sep 17 00:00:00 2001 From: Gord Thompson Date: Tue, 19 Sep 2017 19:06:17 -0600 Subject: [PATCH 1/2] recognize CallableStatement parameter names with leading '@' --- .../jdbc/SQLServerCallableStatement.java | 6 ++ .../CallableStatementTest.java | 63 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java index 40a77be153..15777f4ca2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java @@ -1451,6 +1451,12 @@ public NClob getNClob(String parameterName) throws SQLException { if (paramNames != null) l = paramNames.size(); + // handle `@name` as well as `name`, since `@name` is what's returned + // by DatabaseMetaData#getProcedureColumns + if (columnName.startsWith("@")) { + columnName = columnName.substring(1, columnName.length()); + } + // 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: 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 d0271714d3..9c682e8499 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java @@ -1,9 +1,12 @@ package com.microsoft.sqlserver.jdbc.callablestatement; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; @@ -17,6 +20,7 @@ import com.microsoft.sqlserver.jdbc.SQLServerCallableStatement; import com.microsoft.sqlserver.jdbc.SQLServerDataSource; +import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.testframework.AbstractTest; import com.microsoft.sqlserver.testframework.Utils; @@ -28,6 +32,7 @@ public class CallableStatementTest extends AbstractTest { private static String tableNameGUID = "uniqueidentifier_Table"; private static String outputProcedureNameGUID = "uniqueidentifier_SP"; private static String setNullProcedureName = "CallableStatementTest_setNull_SP"; + private static String inputParamsProcedureName = "CallableStatementTest_inputParams_SP"; private static Connection connection = null; private static Statement stmt = null; @@ -45,10 +50,12 @@ public static void setupTest() throws SQLException { Utils.dropTableIfExists(tableNameGUID, stmt); Utils.dropProcedureIfExists(outputProcedureNameGUID, stmt); Utils.dropProcedureIfExists(setNullProcedureName, stmt); + Utils.dropProcedureIfExists(inputParamsProcedureName, stmt); createGUIDTable(); createGUIDStoredProcedure(); - createSetNullPreocedure(); + createSetNullProcedure(); + createInputParamsProcedure(); } /** @@ -133,6 +140,44 @@ public void getSetNullWithTypeVarchar() throws SQLException { } } + + /** + * recognize parameter names with and without leading '@' + * + * @throws SQLException + */ + @Test + public void inputParamsTest() throws SQLException { + String call = "{CALL " + inputParamsProcedureName + " (?,?)}"; + ResultSet rs = null; + + // the historical way: no leading '@', parameter names respected (not positional) + CallableStatement cs1 = connection.prepareCall(call); + cs1.setString("p2", "bar"); + cs1.setString("p1", "foo"); + rs = cs1.executeQuery(); + rs.next(); + assertEquals("foobar", rs.getString(1)); + + // the "new" way: leading '@', parameter names still respected (not positional) + CallableStatement cs2 = connection.prepareCall(call); + cs2.setString("@p2", "world!"); + cs2.setString("@p1", "Hello "); + rs = cs2.executeQuery(); + rs.next(); + assertEquals("Hello world!", rs.getString(1)); + + // sanity check: unrecognized parameter name + CallableStatement cs3 = connection.prepareCall(call); + try { + cs3.setString("@whatever", "junk"); + fail("SQLServerException should have been thrown"); + } catch (SQLServerException sse) { + // expected + } + + } + /** * Cleanup after test * @@ -143,6 +188,7 @@ public static void cleanup() throws SQLException { Utils.dropTableIfExists(tableNameGUID, stmt); Utils.dropProcedureIfExists(outputProcedureNameGUID, stmt); Utils.dropProcedureIfExists(setNullProcedureName, stmt); + Utils.dropProcedureIfExists(inputParamsProcedureName, stmt); if (null != stmt) { stmt.close(); @@ -162,7 +208,20 @@ private static void createGUIDTable() throws SQLException { stmt.execute(sql); } - private static void createSetNullPreocedure() throws SQLException { + private static void createSetNullProcedure() throws SQLException { stmt.execute("create procedure " + setNullProcedureName + " (@p1 nvarchar(255), @p2 nvarchar(255) output) as select @p2=@p1 return 0"); } + + private static void createInputParamsProcedure() throws SQLException { + String sql = + "CREATE PROCEDURE [dbo].[CallableStatementTest_inputParams_SP] " + + " @p1 nvarchar(max) = N'parameter1', " + + " @p2 nvarchar(max) = N'parameter2' " + + "AS " + + "BEGIN " + + " SET NOCOUNT ON; " + + " SELECT @p1 + @p2 AS result; " + + "END "; + stmt.execute(sql); + } } From e2ec02e6519827d18117726090a56dbf300a1af0 Mon Sep 17 00:00:00 2001 From: Gord Thompson Date: Fri, 6 Oct 2017 15:27:37 -0600 Subject: [PATCH 2/2] tweak to preserve original parameter name for exception message --- .../sqlserver/jdbc/SQLServerCallableStatement.java | 9 ++++++--- .../jdbc/callablestatement/CallableStatementTest.java | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java index 15777f4ca2..5ad8689c86 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java @@ -1453,8 +1453,11 @@ public NClob getNClob(String parameterName) throws SQLException { // handle `@name` as well as `name`, since `@name` is what's returned // by DatabaseMetaData#getProcedureColumns + String columnNameWithoutAtSign = null; if (columnName.startsWith("@")) { - columnName = columnName.substring(1, columnName.length()); + columnNameWithoutAtSign = columnName.substring(1, columnName.length()); + } else { + columnNameWithoutAtSign = columnName; } // In order to be as accurate as possible when locating parameter name @@ -1471,7 +1474,7 @@ public NClob getNClob(String parameterName) throws SQLException { for (i = 0; i < l; i++) { String sParam = paramNames.get(i); sParam = sParam.substring(1, sParam.length()); - if (sParam.equals(columnName)) { + if (sParam.equals(columnNameWithoutAtSign)) { matchPos = i; break; } @@ -1483,7 +1486,7 @@ public NClob getNClob(String parameterName) throws SQLException { for (i = 0; i < l; i++) { String sParam = paramNames.get(i); sParam = sParam.substring(1, sParam.length()); - if (sParam.equalsIgnoreCase(columnName)) { + if (sParam.equalsIgnoreCase(columnNameWithoutAtSign)) { matchPos = i; break; } 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 9c682e8499..ce738ff2ec 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java @@ -173,7 +173,9 @@ public void inputParamsTest() throws SQLException { cs3.setString("@whatever", "junk"); fail("SQLServerException should have been thrown"); } catch (SQLServerException sse) { - // expected + if (!sse.getMessage().startsWith("Parameter @whatever was not defined")) { + fail("Unexpected content in exception message"); + } } }