@@ -72,10 +72,12 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS
7272 final String userSQL ;
7373
7474 // flag whether is exec escape syntax
75- private boolean isExecEscapeSyntax ;
75+ private boolean hasExecEscape ;
7676
7777 // flag whether is call escape syntax
78- private boolean isCallEscapeSyntax ;
78+ private boolean hasCallEscape ;
79+
80+ private boolean hasEmbeddedParam ;
7981
8082 /** Parameter positions in processed SQL statement text. */
8183 final int [] userSQLParamPositions ;
@@ -135,20 +137,6 @@ private void setPreparedStatementHandle(int handle) {
135137 */
136138 private boolean useBulkCopyForBatchInsert ;
137139
138- /**
139- * Regex for JDBC 'call' escape syntax
140- *
141- * Matches {[? =] call sproc ([@arg =] ?, [@arg =] ?, [@arg =] ? ...)}
142- */
143- private static final Pattern callEscapePattern = Pattern
144- .compile ("^\\ s*(?i)\\ {(\\ s*\\ ??\\ s*=?\\ s*)call [^\\ (\\ )]+\\ s*" +
145- "((\\ (\\ s*(.+\\ s*=\\ s*)?\\ ?\\ s*(,\\ s*\\ ?\\ s*)*\\ ))?|\\ (\\ ))\\ s*}" );
146-
147- /**
148- * Regex for 'exec' escape syntax
149- */
150- private static final Pattern execEscapePattern = Pattern .compile ("^\\ s*(?i)(?:exec|execute)\\ b" );
151-
152140 /**
153141 * For caching data related to batch insert with bulkcopy
154142 */
@@ -303,8 +291,9 @@ private boolean resetPrepStmtHandle(boolean discardCurrentCacheItem) {
303291 procedureName = parsedSQL .procedureName ;
304292 bReturnValueSyntax = parsedSQL .bReturnValueSyntax ;
305293 userSQL = parsedSQL .processedSQL ;
306- isExecEscapeSyntax = isExecEscapeSyntax (sql );
307- isCallEscapeSyntax = isCallEscapeSyntax (sql );
294+ hasCallEscape = parsedSQL .callEscape ;
295+ hasExecEscape = parsedSQL .execEscape ;
296+ hasEmbeddedParam = parsedSQL .embeddedParam ;
308297 userSQLParamPositions = parsedSQL .parameterPositions ;
309298 initParams (userSQLParamPositions .length );
310299 useBulkCopyForBatchInsert = conn .getUseBulkCopyForBatchInsert ();
@@ -464,8 +453,16 @@ private boolean buildPreparedStrings(Parameter[] params, boolean renewDefinition
464453
465454 preparedTypeDefinitions = newTypeDefinitions ;
466455
467- /* Replace the parameter marker '?' with the param numbers @p1, @p2 etc */
468- preparedSQL = connection .replaceParameterMarkers (userSQL , userSQLParamPositions , params , bReturnValueSyntax );
456+ if (hasEmbeddedParam && (hasExecEscape || hasCallEscape )) {
457+ // If the CallableStatement has embedded parameter values eg. 'EXEC sp @param0=value0, ?, ?',
458+ // retain these values and replace '?' explicitly in the sql string with the parameter values
459+ // instead of the parameter numbers @p1, @p2, etc... like the else case
460+ preparedSQL = connection .replaceParameterMarkers (userSQL , userSQLParamPositions , params );
461+ } else {
462+ /* Replace the parameter marker '?' with the param numbers @p1, @p2 etc */
463+ preparedSQL = connection .replaceParameterMarkers (userSQL , userSQLParamPositions , params , bReturnValueSyntax );
464+ }
465+
469466 if (bRequestedGeneratedKeys )
470467 preparedSQL = preparedSQL + IDENTITY_QUERY ;
471468
@@ -705,7 +702,7 @@ final void doExecutePreparedStatement(PrepStmtExecCmd command) throws SQLServerE
705702
706703 // Start the request and detach the response reader so that we can
707704 // continue using it after we return.
708- TDSWriter tdsWriter = command .startRequest (TDS .PKT_RPC );
705+ TDSWriter tdsWriter = command .startRequest (hasEmbeddedParam ? TDS . PKT_QUERY : TDS .PKT_RPC );
709706
710707 needsPrepare = doPrepExec (tdsWriter , inOutParam , hasNewTypeDefinitions , hasExistingTypeDefinitions ,
711708 command );
@@ -895,6 +892,24 @@ private void buildRPCExecParams(TDSWriter tdsWriter) throws SQLServerException {
895892 tdsWriter .writeByte ((byte ) 0 ); // RPC procedure option 2
896893 }
897894
895+ private void buildParamsNonRPC (TDSWriter tdsWriter ) throws SQLServerException {
896+ if (getStatementLogger ().isLoggable (java .util .logging .Level .FINE )) {
897+ getStatementLogger ().fine (toString () + ": calling PROC" + ", SQL:" + preparedSQL );
898+ }
899+
900+ expectPrepStmtHandle = false ;
901+ executedSqlDirectly = true ;
902+ expectCursorOutParams = false ;
903+ outParamIndexAdjustment = 0 ;
904+ tdsWriter .writeString (preparedSQL );
905+ if (connection .isAEv2 ()) {
906+ tdsWriter .sendEnclavePackage (preparedSQL , enclaveCEKs );
907+ }
908+
909+ tdsWriter .writeByte ((byte ) 0 ); // RPC procedure option 1
910+ tdsWriter .writeByte ((byte ) 0 ); // RPC procedure option 2
911+ }
912+
898913 private void buildPrepParams (TDSWriter tdsWriter ) throws SQLServerException {
899914 if (getStatementLogger ().isLoggable (java .util .logging .Level .FINE ))
900915 getStatementLogger ().fine (toString () + ": calling sp_prepare: PreparedHandle:"
@@ -1212,7 +1227,7 @@ private boolean doPrepExec(TDSWriter tdsWriter, Parameter[] params, boolean hasN
12121227
12131228 boolean needsPrepare = (hasNewTypeDefinitions && hasExistingTypeDefinitions ) || !hasPreparedStatementHandle ();
12141229 boolean isPrepareMethodSpPrepExec = connection .getPrepareMethod ().equals (PrepareMethod .PREPEXEC .toString ());
1215- boolean callRpcDirectly = callRPCDirectly (params );
1230+ boolean makeRPC = makeRPC (params );
12161231
12171232 // Cursors don't use statement pooling.
12181233 if (isCursorable (executeMethod )) {
@@ -1221,8 +1236,20 @@ private boolean doPrepExec(TDSWriter tdsWriter, Parameter[] params, boolean hasN
12211236 else
12221237 buildServerCursorExecParams (tdsWriter );
12231238 } else {
1224- // if it is a parameterized stored procedure call and is not TVP, use sp_execute directly.
1225- if (needsPrepare && callRpcDirectly ) {
1239+ // If this is a stored procedure with
1240+ // embedded parameters eg EXEC sp @param1=value1,?,?...
1241+ if (hasEmbeddedParam && needsPrepare ) {
1242+ buildParamsNonRPC (tdsWriter );
1243+
1244+ // Return immediately as we don't need to send the parameters over RPC
1245+ // as the parameters should be embedded in the 'preparedSQL' string,
1246+ // which is what is sent to the server
1247+ return needsPrepare ;
1248+
1249+ }
1250+ // If this is a stored procedure, build the RPC call to execute it directly
1251+ // Can't be a cstmt batch call, as using RPC for batch will cause a significant performance drop
1252+ else if (makeRPC && needsPrepare && executeMethod != EXECUTE_BATCH ) {
12261253 buildRPCExecParams (tdsWriter );
12271254 }
12281255 // Move overhead of needing to do prepare & unprepare to only use cases that need more than one execution.
@@ -1254,7 +1281,7 @@ else if (needsPrepare && !connection.getEnablePrepareOnFirstPreparedStatementCal
12541281 }
12551282 }
12561283
1257- sendParamsByRPC (tdsWriter , params , bReturnValueSyntax , callRpcDirectly );
1284+ sendParamsByRPC (tdsWriter , params , bReturnValueSyntax , makeRPC );
12581285
12591286 return needsPrepare ;
12601287 }
@@ -1266,18 +1293,15 @@ else if (needsPrepare && !connection.getEnablePrepareOnFirstPreparedStatementCal
12661293 * @return
12671294 * @throws SQLServerException
12681295 */
1269- boolean callRPCDirectly (Parameter [] params ) throws SQLServerException {
1296+ boolean makeRPC (Parameter [] params ) throws SQLServerException {
12701297 int paramCount = SQLServerConnection .countParams (userSQL );
12711298
12721299 // In order to execute sprocs directly the following must be true:
12731300 // 1. There must be a sproc name
12741301 // 2. There must be parameters
12751302 // 3. Parameters must not be a TVP type
1276- // 4. Compliant CALL escape syntax
1277- // If isExecEscapeSyntax is true, EXEC escape syntax is used then use prior behaviour of
1278- // wrapping call to execute the procedure
1279- return (null != procedureName && paramCount != 0 && !isTVPType (params ) && isCallEscapeSyntax
1280- && !isExecEscapeSyntax );
1303+ // 4. Compliant CALL escape syntax or compliant EXEC escape syntax
1304+ return (null != procedureName && paramCount != 0 && !isTVPType (params ) && (hasCallEscape || hasExecEscape ));
12811305 }
12821306
12831307 /**
@@ -1297,14 +1321,6 @@ private boolean isTVPType(Parameter[] params) throws SQLServerException {
12971321 return false ;
12981322 }
12991323
1300- private boolean isExecEscapeSyntax (String sql ) {
1301- return execEscapePattern .matcher (sql ).find ();
1302- }
1303-
1304- private boolean isCallEscapeSyntax (String sql ) {
1305- return callEscapePattern .matcher (sql ).find ();
1306- }
1307-
13081324 /**
13091325 * Executes sp_prepare to prepare a parameterized statement and sets the prepared statement handle
13101326 *
0 commit comments