diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ParsedSQLMetadata.java b/src/main/java/com/microsoft/sqlserver/jdbc/ParsedSQLMetadata.java index 19c34ebec..b1b8cb6d8 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ParsedSQLMetadata.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ParsedSQLMetadata.java @@ -14,13 +14,13 @@ final class ParsedSQLCacheItem { /** The SQL text AFTER processing. */ String processedSQL; - int parameterCount; + int[] parameterPositions; String procedureName; boolean bReturnValueSyntax; - ParsedSQLCacheItem(String processedSQL, int parameterCount, String procedureName, boolean bReturnValueSyntax) { + ParsedSQLCacheItem(String processedSQL, int[] parameterPositions, String procedureName, boolean bReturnValueSyntax) { this.processedSQL = processedSQL; - this.parameterCount = parameterCount; + this.parameterPositions = parameterPositions; this.procedureName = procedureName; this.bReturnValueSyntax = bReturnValueSyntax; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index def6d273a..f44b7cafc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -36,6 +36,7 @@ import java.sql.Statement; import java.sql.Struct; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; @@ -123,11 +124,13 @@ static class CityHash128Key { CityHash128Key(String sql, String parametersDefinition) { - this(String.format("%s%s", sql, parametersDefinition)); + this(sql + parametersDefinition); } CityHash128Key(String s) { - segments = CityHash.cityHash128(s.getBytes(), 0, s.length()); + byte[] bytes = new byte[s.length()]; + s.getBytes(0, s.length(), bytes, 0); + segments = CityHash.cityHash128(bytes, 0, bytes.length); } public boolean equals(Object obj) { @@ -260,9 +263,9 @@ static ParsedSQLCacheItem parseAndCacheSQL(CityHash128Key key, String sql) thro String parsedSql = translator.translate(sql); String procName = translator.getProcedureName(); // may return null boolean returnValueSyntax = translator.hasReturnValueSyntax(); - int paramCount = countParams(parsedSql); + int[] parameterPositions = locateParams(parsedSql); - ParsedSQLCacheItem cacheItem = new ParsedSQLCacheItem (parsedSql, paramCount, procName, returnValueSyntax); + ParsedSQLCacheItem cacheItem = new ParsedSQLCacheItem (parsedSql, parameterPositions, procName, returnValueSyntax); parsedSQLCache.putIfAbsent(key, cacheItem); return cacheItem; } @@ -283,22 +286,28 @@ static ParsedSQLCacheItem parseAndCacheSQL(CityHash128Key key, String sql) thro private boolean disableStatementPooling = true; /** - * Find statement parameters. + * Locate statement parameters. * * @param sql - * SQL text to parse for number of parameters to intialize. + * SQL text to parse for positions of parameters to intialize. */ - private static int countParams(String sql) { - int nParams = 0; - - // Figure out the expected number of parameters by counting the - // parameter placeholders in the SQL string. - int offset = -1; - while ((offset = ParameterUtils.scanSQLForChar('?', sql, ++offset)) < sql.length()) - ++nParams; - - return nParams; - } + private static int[] locateParams(String sql) { + List parameterPositions = new ArrayList(0); + + // Locate the parameter placeholders in the SQL string. + int offset = -1; + while ((offset = ParameterUtils.scanSQLForChar('?', sql, ++offset)) < sql.length()) { + parameterPositions.add(offset); + } + + // Convert to int[] + int[] result = new int[parameterPositions.size()]; + int i = 0; + for (Integer parameterPosition : parameterPositions) { + result[i++] = parameterPosition; + } + return result; + } SqlFedAuthToken getAuthenticationResult() { return fedAuthToken; @@ -5309,6 +5318,7 @@ public void endRequest() throws SQLFeatureNotSupportedException { static final char[] OUT = {' ', 'O', 'U', 'T'}; /* L0 */ String replaceParameterMarkers(String sqlSrc, + int[] paramPositions, Parameter[] params, boolean isReturnValueSyntax) throws SQLServerException { final int MAX_PARAM_NAME_LEN = 6; @@ -5319,7 +5329,13 @@ public void endRequest() throws SQLFeatureNotSupportedException { int paramIndex = 0; while (true) { - int srcEnd = ParameterUtils.scanSQLForChar('?', sqlSrc, srcBegin); + int srcEnd; + if (paramIndex >= paramPositions.length) { + srcEnd = sqlSrc.length(); + } + else { + srcEnd = paramPositions[paramIndex]; + } sqlSrc.getChars(srcBegin, srcEnd, sqlDst, dstBegin); dstBegin += srcEnd - srcBegin; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java index dca580ec9..a570c0dba 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java @@ -608,6 +608,7 @@ private void checkClosed() throws SQLServerException { if (con.getServerMajorVersion() >= SQL_SERVER_2012_VERSION) { // new implementation for SQL verser 2012 and above String preparedSQL = con.replaceParameterMarkers(((SQLServerPreparedStatement) stmtParent).userSQL, + ((SQLServerPreparedStatement) stmtParent).userSQLParamPositions, ((SQLServerPreparedStatement) stmtParent).inOutParam, ((SQLServerPreparedStatement) stmtParent).bReturnValueSyntax); SQLServerCallableStatement cstmt = (SQLServerCallableStatement) con.prepareCall("exec sp_describe_undeclared_parameters ?"); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index ee7cb85f7..be1a7f713 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -64,6 +64,9 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS /** Processed SQL statement text, may not be same as what user initially passed. */ final String userSQL; + /** Parameter positions in processed SQL statement text. */ + final int[] userSQLParamPositions; + /** SQL statement with expanded parameter tokens */ private String preparedSQL; @@ -204,7 +207,8 @@ String getClassNameInternal() { procedureName = parsedSQL.procedureName; bReturnValueSyntax = parsedSQL.bReturnValueSyntax; userSQL = parsedSQL.processedSQL; - initParams(parsedSQL.parameterCount); + userSQLParamPositions = parsedSQL.parameterPositions; + initParams(userSQLParamPositions.length); } /** @@ -342,7 +346,7 @@ private boolean buildPreparedStrings(Parameter[] params, preparedTypeDefinitions = newTypeDefinitions; /* Replace the parameter marker '?' with the param numbers @p1, @p2 etc */ - preparedSQL = connection.replaceParameterMarkers(userSQL, params, bReturnValueSyntax); + preparedSQL = connection.replaceParameterMarkers(userSQL, userSQLParamPositions, params, bReturnValueSyntax); if (bRequestedGeneratedKeys) preparedSQL = preparedSQL + identityQuery;