Skip to content

Commit e2685b4

Browse files
authored
Re-added support for stored procedure 'exec' escape syntax in CallableStatements (#2325) (#2329)
* EXEC system stored procedure regression fix * Additional test * Additional test * Indenting * Switched error string to TestResource error string * CR comments * Test update p1 * Test update p2 * CR comment changes; Test update * call escape syntax check * CR changes * formatting
1 parent 3cd946e commit e2685b4

File tree

4 files changed

+137
-17
lines changed

4 files changed

+137
-17
lines changed

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -2064,7 +2064,8 @@ private void writeNullToTdsWriter(TDSWriter tdsWriter, int srcJdbcType,
20642064

20652065
private void writeColumnToTdsWriter(TDSWriter tdsWriter, int bulkPrecision, int bulkScale, int bulkJdbcType,
20662066
boolean bulkNullable, // should it be destNullable instead?
2067-
int srcColOrdinal, int destColOrdinal, boolean isStreaming, Object colValue, Calendar cal) throws SQLServerException {
2067+
int srcColOrdinal, int destColOrdinal, boolean isStreaming, Object colValue,
2068+
Calendar cal) throws SQLServerException {
20682069
SSType destSSType = destColumnMetadata.get(destColOrdinal).ssType;
20692070

20702071
bulkPrecision = validateSourcePrecision(bulkPrecision, bulkJdbcType,
@@ -2987,8 +2988,8 @@ private Object readColumnFromResultSet(int srcColOrdinal, int srcJdbcType, boole
29872988
/**
29882989
* Reads the given column from the result set current row and writes the data to tdsWriter.
29892990
*/
2990-
private void writeColumn(TDSWriter tdsWriter, int srcColOrdinal, int destColOrdinal,
2991-
Object colValue, Calendar cal) throws SQLServerException {
2991+
private void writeColumn(TDSWriter tdsWriter, int srcColOrdinal, int destColOrdinal, Object colValue,
2992+
Calendar cal) throws SQLServerException {
29922993
String destName = destColumnMetadata.get(destColOrdinal).columnName;
29932994
int srcPrecision, srcScale, destPrecision, srcJdbcType;
29942995
SSType destSSType = null;
@@ -3640,8 +3641,8 @@ private boolean writeBatchData(TDSWriter tdsWriter, TDSCommand command,
36403641
// Loop for each destination column. The mappings is a many to one mapping
36413642
// where multiple source columns can be mapped to one destination column.
36423643
for (ColumnMapping columnMapping : columnMappings) {
3643-
writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal, null,
3644-
null // cell
3644+
writeColumn(tdsWriter, columnMapping.sourceColumnOrdinal, columnMapping.destinationColumnOrdinal,
3645+
null, null // cell
36453646
// value is
36463647
// retrieved
36473648
// inside

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java

+36-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Map.Entry;
3030
import java.util.Vector;
3131
import java.util.logging.Level;
32+
import java.util.regex.Pattern;
3233

3334
import com.microsoft.sqlserver.jdbc.SQLServerConnection.CityHash128Key;
3435
import com.microsoft.sqlserver.jdbc.SQLServerConnection.PreparedStatementHandle;
@@ -70,6 +71,10 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS
7071
/** Processed SQL statement text, may not be same as what user initially passed. */
7172
final String userSQL;
7273

74+
private boolean isExecEscapeSyntax;
75+
76+
private boolean isCallEscapeSyntax;
77+
7378
/** Parameter positions in processed SQL statement text. */
7479
final int[] userSQLParamPositions;
7580

@@ -128,6 +133,17 @@ private void setPreparedStatementHandle(int handle) {
128133
*/
129134
private boolean useBulkCopyForBatchInsert;
130135

136+
/**
137+
* Regex for JDBC 'call' escape syntax
138+
*/
139+
private static final Pattern callEscapePattern = Pattern
140+
.compile("^\\s*(?i)\\{(\\s*\\??\\s*=?\\s*)call (.+)\\s*\\(?\\?*,?\\)?\\s*}\\s*$");
141+
142+
/**
143+
* Regex for 'exec' escape syntax
144+
*/
145+
private static final Pattern execEscapePattern = Pattern.compile("^\\s*(?i)(?:exec|execute)\\b");
146+
131147
/** Returns the prepared statement SQL */
132148
@Override
133149
public String toString() {
@@ -253,6 +269,8 @@ private boolean resetPrepStmtHandle(boolean discardCurrentCacheItem) {
253269
procedureName = parsedSQL.procedureName;
254270
bReturnValueSyntax = parsedSQL.bReturnValueSyntax;
255271
userSQL = parsedSQL.processedSQL;
272+
isExecEscapeSyntax = isExecEscapeSyntax(sql);
273+
isCallEscapeSyntax = isCallEscapeSyntax(sql);
256274
userSQLParamPositions = parsedSQL.parameterPositions;
257275
initParams(userSQLParamPositions.length);
258276
useBulkCopyForBatchInsert = conn.getUseBulkCopyForBatchInsert();
@@ -1210,7 +1228,16 @@ else if (needsPrepare && !connection.getEnablePrepareOnFirstPreparedStatementCal
12101228
*/
12111229
boolean callRPCDirectly(Parameter[] params) throws SQLServerException {
12121230
int paramCount = SQLServerConnection.countParams(userSQL);
1213-
return (null != procedureName && paramCount != 0 && !isTVPType(params));
1231+
1232+
// In order to execute sprocs directly the following must be true:
1233+
// 1. There must be a sproc name
1234+
// 2. There must be parameters
1235+
// 3. Parameters must not be a TVP type
1236+
// 4. Compliant CALL escape syntax
1237+
// If isExecEscapeSyntax is true, EXEC escape syntax is used then use prior behaviour to
1238+
// execute the procedure
1239+
return (null != procedureName && paramCount != 0 && !isTVPType(params) && isCallEscapeSyntax
1240+
&& !isExecEscapeSyntax);
12141241
}
12151242

12161243
/**
@@ -1230,6 +1257,14 @@ private boolean isTVPType(Parameter[] params) throws SQLServerException {
12301257
return false;
12311258
}
12321259

1260+
private boolean isExecEscapeSyntax(String sql) {
1261+
return execEscapePattern.matcher(sql).find();
1262+
}
1263+
1264+
private boolean isCallEscapeSyntax(String sql) {
1265+
return callEscapePattern.matcher(sql).find();
1266+
}
1267+
12331268
/**
12341269
* Executes sp_prepare to prepare a parameterized statement and sets the prepared statement handle
12351270
*

src/test/java/com/microsoft/sqlserver/jdbc/callablestatement/CallableStatementTest.java

+89-6
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public class CallableStatementTest extends AbstractTest {
8181

8282
/**
8383
* Setup before test
84-
*
84+
*
8585
* @throws SQLException
8686
*/
8787
@BeforeAll
@@ -201,7 +201,7 @@ public void testCallableStatementSpPrepare() throws SQLException {
201201

202202
/**
203203
* Tests CallableStatement.getString() with uniqueidentifier parameter
204-
*
204+
*
205205
* @throws SQLException
206206
*/
207207
@Test
@@ -226,7 +226,7 @@ public void getStringGUIDTest() throws SQLException {
226226

227227
/**
228228
* test for setNull(index, varchar) to behave as setNull(index, nvarchar) when SendStringParametersAsUnicode is true
229-
*
229+
*
230230
* @throws SQLException
231231
*/
232232
@Test
@@ -302,7 +302,7 @@ public void testGetObjectAsLocalDateTime() throws SQLException {
302302

303303
/**
304304
* Tests getObject(n, java.time.OffsetDateTime.class) and getObject(n, java.time.OffsetTime.class).
305-
*
305+
*
306306
* @throws SQLException
307307
*/
308308
@Test
@@ -332,7 +332,7 @@ public void testGetObjectAsOffsetDateTime() throws SQLException {
332332

333333
/**
334334
* recognize parameter names with and without leading '@'
335-
*
335+
*
336336
* @throws SQLException
337337
*/
338338
@Test
@@ -1067,9 +1067,92 @@ public void testRegisteringOutputByIndexandAcquiringOutputParamByName() throws S
10671067
}
10681068
}
10691069

1070+
@Test
1071+
public void testExecuteSystemStoredProcedureNamedParametersAndIndexedParameterNoResultset() throws SQLException {
1072+
String call0 = "EXEC sp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'";
1073+
String call1 = "\rEXEC\r\rsp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'";
1074+
String call2 = " EXEC sp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'";
1075+
String call3 = "\tEXEC\t\t\tsp_getapplock @Resource=?, @LockTimeout='0', @LockMode='Exclusive', @LockOwner='Session'";
1076+
1077+
try (CallableStatement cstmt0 = connection.prepareCall(call0);
1078+
CallableStatement cstmt1 = connection.prepareCall(call1);
1079+
CallableStatement cstmt2 = connection.prepareCall(call2);
1080+
CallableStatement cstmt3 = connection.prepareCall(call3);) {
1081+
cstmt0.setString(1, "Resource-" + UUID.randomUUID());
1082+
cstmt0.execute();
1083+
1084+
cstmt1.setString(1, "Resource-" + UUID.randomUUID());
1085+
cstmt1.execute();
1086+
1087+
cstmt2.setString(1, "Resource-" + UUID.randomUUID());
1088+
cstmt2.execute();
1089+
1090+
cstmt3.setString(1, "Resource-" + UUID.randomUUID());
1091+
cstmt3.execute();
1092+
}
1093+
}
1094+
1095+
@Test
1096+
public void testExecSystemStoredProcedureNamedParametersAndIndexedParameterResultSet() throws SQLException {
1097+
String call = "exec sp_sproc_columns_100 ?, @ODBCVer=3, @fUsePattern=0";
1098+
1099+
try (CallableStatement cstmt = connection.prepareCall(call)) {
1100+
cstmt.setString(1, "sp_getapplock");
1101+
1102+
try (ResultSet rs = cstmt.executeQuery()) {
1103+
while (rs.next()) {
1104+
assertTrue(TestResource.getResource("R_resultSetEmpty"), !rs.getString(4).isEmpty());
1105+
}
1106+
}
1107+
}
1108+
}
1109+
1110+
@Test
1111+
public void testExecSystemStoredProcedureNoIndexedParametersResultSet() throws SQLException {
1112+
String call = "execute sp_sproc_columns_100 sp_getapplock, @ODBCVer=3, @fUsePattern=0";
1113+
1114+
try (CallableStatement cstmt = connection.prepareCall(call); ResultSet rs = cstmt.executeQuery()) {
1115+
while (rs.next()) {
1116+
assertTrue(TestResource.getResource("R_resultSetEmpty"), !rs.getString(4).isEmpty());
1117+
}
1118+
}
1119+
}
1120+
1121+
@Test
1122+
public void testExecDocumentedSystemStoredProceduresIndexedParameters() throws SQLException {
1123+
String serverName;
1124+
String testTableName = "testTable";
1125+
Integer integer = new Integer(1);
1126+
1127+
try (Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT @@SERVERNAME")) {
1128+
rs.next();
1129+
serverName = rs.getString(1);
1130+
}
1131+
1132+
String[] sprocs = {"EXEC sp_column_privileges ?", "exec sp_catalogs ?", "execute sp_column_privileges ?",
1133+
"EXEC sp_column_privileges_ex ?", "EXECUTE sp_columns ?", "execute sp_datatype_info ?",
1134+
"EXEC sp_sproc_columns ?", "EXECUTE sp_server_info ?", "exec sp_special_columns ?",
1135+
"execute sp_statistics ?", "EXEC sp_table_privileges ?", "exec sp_tables ?"};
1136+
1137+
Object[] params = {testTableName, serverName, testTableName, serverName, testTableName, integer,
1138+
"sp_column_privileges", integer, testTableName, testTableName, testTableName, testTableName};
1139+
1140+
int paramIndex = 0;
1141+
1142+
for (String sproc : sprocs) {
1143+
try (CallableStatement cstmt = connection.prepareCall(sproc)) {
1144+
cstmt.setObject(1, params[paramIndex]);
1145+
cstmt.execute();
1146+
paramIndex++;
1147+
} catch (Exception e) {
1148+
fail("Failed executing '" + sproc + "' with indexed parameter '" + params[paramIndex]);
1149+
}
1150+
}
1151+
}
1152+
10701153
/**
10711154
* Cleanup after test
1072-
*
1155+
*
10731156
* @throws SQLException
10741157
*/
10751158
@AfterAll

src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/BatchExecutionTest.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public void testValidTimezoneForTimestampBatchInsertWithBulkCopy() throws Except
170170
public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Exception {
171171
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
172172

173-
for (String tzId: TimeZone.getAvailableIDs()) {
173+
for (String tzId : TimeZone.getAvailableIDs()) {
174174
TimeZone.setDefault(TimeZone.getTimeZone(tzId));
175175

176176
long ms = 1696127400000L; // DST
@@ -191,8 +191,8 @@ public void testValidTimezonesDstTimestampBatchInsertWithBulkCopy() throws Excep
191191
}
192192

193193
// Insert Timestamp using bulkcopy for batch insert
194-
try (Connection con = DriverManager.getConnection(
195-
connectionString + ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;");
194+
try (Connection con = DriverManager.getConnection(connectionString
195+
+ ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;");
196196
PreparedStatement pstmt = con.prepareStatement("INSERT INTO " + timestampTable1 + " VALUES(?)")) {
197197

198198
Timestamp timestamp = new Timestamp(ms);
@@ -235,8 +235,9 @@ public void testBatchInsertTimestampNoTimezoneDoubleConversion() throws Exceptio
235235
long ms = 1578743412000L;
236236

237237
// Insert Timestamp using prepared statement when useBulkCopyForBatchInsert=true
238-
try (Connection con = DriverManager.getConnection(connectionString
239-
+ ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;"); Statement stmt = con.createStatement();
238+
try (Connection con = DriverManager.getConnection(
239+
connectionString + ";useBulkCopyForBatchInsert=true;sendTemporalDataTypesAsStringForBulkCopy=false;");
240+
Statement stmt = con.createStatement();
240241
PreparedStatement pstmt = con.prepareStatement("INSERT INTO " + timestampTable2 + " VALUES(?)")) {
241242

242243
TestUtils.dropTableIfExists(timestampTable2, stmt);

0 commit comments

Comments
 (0)