Skip to content

Commit 7ea1bb9

Browse files
committed
Exec cstmt directly to date Pt.2
1 parent bdccdd6 commit 7ea1bb9

File tree

6 files changed

+242
-47
lines changed

6 files changed

+242
-47
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@ final class ParsedSQLCacheItem {
1414
int[] parameterPositions;
1515
String procedureName;
1616
boolean bReturnValueSyntax;
17+
boolean callEscape;
18+
boolean execEscape;
19+
boolean embeddedParam;
1720

1821
ParsedSQLCacheItem(String processedSQL, int[] parameterPositions, String procedureName,
19-
boolean bReturnValueSyntax) {
22+
boolean bReturnValueSyntax, boolean callEscape, boolean execEscape, boolean embeddedParam) {
2023
this.processedSQL = processedSQL;
2124
this.parameterPositions = parameterPositions;
2225
this.procedureName = procedureName;
2326
this.bReturnValueSyntax = bReturnValueSyntax;
27+
this.callEscape = callEscape;
28+
this.execEscape = execEscape;
29+
this.embeddedParam = embeddedParam;
2430
}
2531
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ private Parameter getOutParameter(int i) throws SQLServerException {
208208
// the stored procedure for cursorable ones differently ( calling sp_cursorexecute r sp_cursorprepexec.
209209
if (bReturnValueSyntax && inOutParam[i - 1].isValueGotten() && inOutParam[i - 1].isReturnValue()
210210
&& !isReturnValueAccessed && !isCursorable(executeMethod) && !isTVPType
211-
&& callRPCDirectly(inOutParam)) {
211+
&& makeRPC(inOutParam)) {
212212
nOutParamsAssigned++;
213213
isReturnValueAccessed = true;
214214
}
@@ -364,7 +364,7 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException {
364364
OutParamHandler outParamHandler = new OutParamHandler();
365365

366366
if (bReturnValueSyntax && (nOutParamsAssigned == 0) && !isCursorable(executeMethod) && !isTVPType
367-
&& callRPCDirectly(inOutParam) && returnValueStatus != USER_DEFINED_FUNCTION_RETURN_STATUS) {
367+
&& makeRPC(inOutParam) && returnValueStatus != USER_DEFINED_FUNCTION_RETURN_STATUS) {
368368
nOutParamsAssigned++;
369369
}
370370

@@ -412,7 +412,7 @@ && callRPCDirectly(inOutParam) && returnValueStatus != USER_DEFINED_FUNCTION_RET
412412
// considered to be an output parameter.
413413
outParamIndex = outParamHandler.srv.getOrdinalOrLength();
414414

415-
if (bReturnValueSyntax && !isCursorable(executeMethod) && !isTVPType && callRPCDirectly(inOutParam)
415+
if (bReturnValueSyntax && !isCursorable(executeMethod) && !isTVPType && makeRPC(inOutParam)
416416
&& returnValueStatus != USER_DEFINED_FUNCTION_RETURN_STATUS) {
417417
outParamIndex++;
418418
} else {

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

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99

1010
import java.io.IOException;
1111
import java.io.Serializable;
12+
import java.math.BigDecimal;
1213
import java.net.DatagramPacket;
1314
import java.net.DatagramSocket;
1415
import java.net.InetAddress;
1516
import java.net.InetSocketAddress;
1617
import java.net.SocketException;
1718
import java.net.UnknownHostException;
19+
import java.sql.Blob;
1820
import java.sql.CallableStatement;
21+
import java.sql.Clob;
1922
import java.sql.Connection;
2023
import java.sql.DatabaseMetaData;
2124
import java.sql.PreparedStatement;
@@ -554,10 +557,13 @@ static ParsedSQLCacheItem parseAndCacheSQL(CityHash128Key key, String sql) throw
554557
String parsedSql = translator.translate(sql);
555558
String procName = translator.getProcedureName(); // may return null
556559
boolean returnValueSyntax = translator.hasReturnValueSyntax();
560+
boolean callEscape = translator.hasCallEscape();
561+
boolean execEscape = translator.hasExecEscape();
562+
boolean embeddedParam = translator.hasEmbeddedParam();
557563
int[] parameterPositions = locateParams(parsedSql);
558564

559565
ParsedSQLCacheItem cacheItem = new ParsedSQLCacheItem(parsedSql, parameterPositions, procName,
560-
returnValueSyntax);
566+
returnValueSyntax, callEscape, execEscape, embeddedParam);
561567
parsedSQLCache.putIfAbsent(key, cacheItem);
562568
return cacheItem;
563569
}
@@ -8070,7 +8076,7 @@ String replaceParameterMarkers(String sqlSrc, int[] paramPositions, Parameter[]
80708076

80718077
int paramIndex = 0;
80728078
while (true) {
8073-
int srcEnd = ParameterUtils.scanSQLForChar('?', sqlSrc, srcBegin);
8079+
int srcEnd = (paramIndex >= paramPositions.length) ? sqlSrc.length() : paramPositions[paramIndex];
80748080
sqlSrc.getChars(srcBegin, srcEnd, sqlDst, dstBegin);
80758081
dstBegin += srcEnd - srcBegin;
80768082

@@ -8089,6 +8095,111 @@ String replaceParameterMarkers(String sqlSrc, int[] paramPositions, Parameter[]
80898095
return new String(sqlDst, 0, dstBegin);
80908096
}
80918097

8098+
String replaceParameterMarkers(String sqlSrc, int[] paramPositions, Parameter[] params) throws SQLServerException {
8099+
StringBuilder buf = new StringBuilder(sqlSrc);
8100+
try {
8101+
int paramIndex = 0;
8102+
8103+
for (int pos : paramPositions) {
8104+
buf.replace(pos, pos + 1, normalizeData(((AppDTVImpl)(params[paramIndex++]
8105+
.getInputDTV().impl)).value, sendStringParametersAsUnicode));
8106+
}
8107+
8108+
} catch (Exception e) {
8109+
SQLServerException.makeFromDriverError(null, null, e.getMessage(), null, true);
8110+
}
8111+
8112+
return buf.toString();
8113+
}
8114+
8115+
private static final char hex[] = {'0', '1', '2', '3', '4', '5', '6','7',
8116+
'8', '9', 'A', 'B', 'C', 'D', 'E','F'
8117+
};
8118+
8119+
static String normalizeData(Object value, boolean isUnicode) throws SQLException {
8120+
StringBuilder sb = new StringBuilder();
8121+
8122+
if (value instanceof Blob) {
8123+
Blob blob = (Blob) value;
8124+
8125+
value = blob.getBytes(1, (int) blob.length());
8126+
} else if (value instanceof Clob) {
8127+
Clob clob = (Clob) value;
8128+
8129+
value = clob.getSubString(1, (int) clob.length());
8130+
}
8131+
8132+
if (value instanceof byte[]) {
8133+
byte[] bytes = (byte[]) value;
8134+
8135+
int len = bytes.length;
8136+
8137+
if (len >= 0) {
8138+
sb.append('0').append('x');
8139+
if (len == 0) {
8140+
// Zero length binary values are not allowed
8141+
sb.append('0').append('0');
8142+
} else {
8143+
for (int i = 0; i < len; i++) {
8144+
int b1 = bytes[i] & 0xFF;
8145+
8146+
sb.append(hex[b1 >> 4]);
8147+
sb.append(hex[b1 & 0x0F]);
8148+
}
8149+
}
8150+
}
8151+
8152+
} else if (value instanceof String) {
8153+
String tmp = (String) value;
8154+
int len = tmp.length();
8155+
8156+
if (isUnicode) {
8157+
sb.append('N');
8158+
}
8159+
sb.append('\'');
8160+
8161+
for (int i = 0; i < len; i++) {
8162+
char c = tmp.charAt(i);
8163+
8164+
if (c == '\'') {
8165+
sb.append('\'');
8166+
}
8167+
8168+
sb.append(c);
8169+
}
8170+
8171+
sb.append('\'');
8172+
} else if (value instanceof java.sql.Date
8173+
|| value instanceof java.sql.Time
8174+
|| value instanceof java.sql.Timestamp
8175+
|| value instanceof microsoft.sql.DateTimeOffset) {
8176+
sb.append('\'');
8177+
sb.append(value.toString());
8178+
sb.append('\'');
8179+
} else if (value instanceof Boolean) {
8180+
sb.append(((Boolean) value).booleanValue() ? '1' : '0');
8181+
8182+
} else if (value instanceof BigDecimal) {
8183+
String tmp = value.toString();
8184+
int maxlen = MAX_DECIMAL_PRECISION;
8185+
if (tmp.charAt(0) == '-') {
8186+
maxlen++;
8187+
}
8188+
if (tmp.indexOf('.') >= 0) {
8189+
maxlen++;
8190+
}
8191+
if (tmp.length() > maxlen) {
8192+
sb.append(tmp.substring(0, maxlen));
8193+
} else {
8194+
sb.append(tmp);
8195+
}
8196+
} else {
8197+
sb.append(value.toString());
8198+
}
8199+
8200+
return sb.toString();
8201+
}
8202+
80928203
/**
80938204
* Makes a SQL Server style parameter name.
80948205
*

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

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)