From e2cf2174fdf4819b5a6de797d7a9bda4479cb6fb Mon Sep 17 00:00:00 2001 From: Cheena Malhotra Date: Mon, 9 Jul 2018 12:56:25 -0700 Subject: [PATCH] Change Sha1HashKey to CityHash128Key for generating PreparedStatement handle and metadata cache keys (#717) * Change Sha1HashKey to CityHash128Key * Formatted code * Prepared statement performance fixes 1) Further speedups to prepared statement hashing 2) Caching of '?' chararacter positiobs in prepared statements to speed parameter substitution * String compare for hash keys added missing line for bulkcopy tests. * comment change * Move CityHash class to a separate file * spacings fixes cleaner code & logic --- .../microsoft/sqlserver/jdbc/CityHash.java | 333 ++++++++++++++++++ .../sqlserver/jdbc/ParsedSQLMetadata.java | 6 +- .../sqlserver/jdbc/SQLServerConnection.java | 132 ++++--- .../jdbc/SQLServerParameterMetaData.java | 1 + .../jdbc/SQLServerPreparedStatement.java | 18 +- .../sqlserver/jdbc/SQLServerStatement.java | 4 +- .../com/microsoft/sqlserver/jdbc/Util.java | 14 +- 7 files changed, 421 insertions(+), 87 deletions(-) create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/CityHash.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/CityHash.java b/src/main/java/com/microsoft/sqlserver/jdbc/CityHash.java new file mode 100644 index 0000000000..e8d21c73fd --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/CityHash.java @@ -0,0 +1,333 @@ +package com.microsoft.sqlserver.jdbc; + +/** + * Copyright (C) 2012 tamtam180 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + * + * @author tamtam180 - kirscheless at gmail.com + * @see http://google-opensource.blogspot.jp/2011/04/introducing-cityhash.html + * @see http://code.google.com/p/cityhash/ + */ +final class CityHash { + + private static final long k0 = 0xc3a5c85c97cb3127L; + private static final long k1 = 0xb492b66fbe98f273L; + private static final long k2 = 0x9ae16a3b2f90404fL; + private static final long k3 = 0xc949d7c7509e6557L; + + private static long toLongLE(byte[] b, int i) { + return (((long) b[i + 7] << 56) + ((long) (b[i + 6] & 255) << 48) + + ((long) (b[i + 5] & 255) << 40) + + ((long) (b[i + 4] & 255) << 32) + + ((long) (b[i + 3] & 255) << 24) + ((b[i + 2] & 255) << 16) + + ((b[i + 1] & 255) << 8) + ((b[i + 0] & 255) << 0)); + } + + private static int toIntLE(byte[] b, int i) { + return (((b[i + 3] & 255) << 24) + ((b[i + 2] & 255) << 16) + + ((b[i + 1] & 255) << 8) + ((b[i + 0] & 255) << 0)); + } + + private static long fetch64(byte[] s, int pos) { + return toLongLE(s, pos); + } + + private static int fetch32(byte[] s, int pos) { + return toIntLE(s, pos); + } + + private static long rotate(long val, int shift) { + return shift == 0 ? val : (val >>> shift) | (val << (64 - shift)); + } + + private static long rotateByAtLeast1(long val, int shift) { + return (val >>> shift) | (val << (64 - shift)); + } + + private static long shiftMix(long val) { + return val ^ (val >>> 47); + } + + private static final long kMul = 0x9ddfea08eb382d69L; + + private static long hash128to64(long u, long v) { + long a = (u ^ v) * kMul; + a ^= (a >>> 47); + long b = (v ^ a) * kMul; + b ^= (b >>> 47); + b *= kMul; + return b; + } + + private static long hashLen16(long u, long v) { + return hash128to64(u, v); + } + + private static long hashLen0to16(byte[] s, int pos, int len) { + if (len > 8) { + long a = fetch64(s, pos + 0); + long b = fetch64(s, pos + len - 8); + return hashLen16(a, rotateByAtLeast1(b + len, len)) ^ b; + } + if (len >= 4) { + long a = 0xffffffffL & fetch32(s, pos + 0); + return hashLen16((a << 3) + len, + 0xffffffffL & fetch32(s, pos + len - 4)); + } + if (len > 0) { + int a = s[pos + 0] & 0xFF; + int b = s[pos + (len >>> 1)] & 0xFF; + int c = s[pos + len - 1] & 0xFF; + int y = a + (b << 8); + int z = len + (c << 2); + return shiftMix(y * k2 ^ z * k3) * k2; + } + return k2; + } + + private static long hashLen17to32(byte[] s, int pos, int len) { + long a = fetch64(s, pos + 0) * k1; + long b = fetch64(s, pos + 8); + long c = fetch64(s, pos + len - 8) * k2; + long d = fetch64(s, pos + len - 16) * k0; + return hashLen16(rotate(a - b, 43) + rotate(c, 30) + d, + a + rotate(b ^ k3, 20) - c + len); + } + + private static long[] weakHashLen32WithSeeds(long w, long x, long y, long z, + long a, long b) { + + a += w; + b = rotate(b + a + z, 21); + long c = a; + a += x; + a += y; + b += rotate(a, 44); + return new long[]{a + z, b + c}; + } + + private static long[] weakHashLen32WithSeeds(byte[] s, int pos, long a, + long b) { + return weakHashLen32WithSeeds(fetch64(s, pos + 0), fetch64(s, pos + 8), + fetch64(s, pos + 16), fetch64(s, pos + 24), a, b); + } + + private static long hashLen33to64(byte[] s, int pos, int len) { + + long z = fetch64(s, pos + 24); + long a = fetch64(s, pos + 0) + (fetch64(s, pos + len - 16) + len) * k0; + long b = rotate(a + z, 52); + long c = rotate(a, 37); + + a += fetch64(s, pos + 8); + c += rotate(a, 7); + a += fetch64(s, pos + 16); + + long vf = a + z; + long vs = b + rotate(a, 31) + c; + + a = fetch64(s, pos + 16) + fetch64(s, pos + len - 32); + z = fetch64(s, pos + len - 8); + b = rotate(a + z, 52); + c = rotate(a, 37); + a += fetch64(s, pos + len - 24); + c += rotate(a, 7); + a += fetch64(s, pos + len - 16); + + long wf = a + z; + long ws = b + rotate(a, 31) + c; + long r = shiftMix((vf + ws) * k2 + (wf + vs) * k0); + + return shiftMix(r * k0 + vs) * k2; + + } + + static long cityHash64(byte[] s, int pos, int len) { + + if (len <= 32) { + if (len <= 16) { + return hashLen0to16(s, pos, len); + } else { + return hashLen17to32(s, pos, len); + } + } else if (len <= 64) { + return hashLen33to64(s, pos, len); + } + + long x = fetch64(s, pos + len - 40); + long y = fetch64(s, pos + len - 16) + fetch64(s, pos + len - 56); + long z = hashLen16(fetch64(s, pos + len - 48) + len, + fetch64(s, pos + len - 24)); + + long[] v = weakHashLen32WithSeeds(s, pos + len - 64, len, z); + long[] w = weakHashLen32WithSeeds(s, pos + len - 32, y + k1, x); + x = x * k1 + fetch64(s, pos + 0); + + len = (len - 1) & (~63); + do { + x = rotate(x + y + v[0] + fetch64(s, pos + 8), 37) * k1; + y = rotate(y + v[1] + fetch64(s, pos + 48), 42) * k1; + x ^= w[1]; + y += v[0] + fetch64(s, pos + 40); + z = rotate(z + w[0], 33) * k1; + v = weakHashLen32WithSeeds(s, pos + 0, v[1] * k1, x + w[0]); + w = weakHashLen32WithSeeds(s, pos + 32, z + w[1], + y + fetch64(s, pos + 16)); + { + long swap = z; + z = x; + x = swap; + } + pos += 64; + len -= 64; + } while (len != 0); + + return hashLen16(hashLen16(v[0], w[0]) + shiftMix(y) * k1 + z, + hashLen16(v[1], w[1]) + x); + + } + + static long cityHash64WithSeed(byte[] s, int pos, int len, long seed) { + return cityHash64WithSeeds(s, pos, len, k2, seed); + } + + static long cityHash64WithSeeds(byte[] s, int pos, int len, long seed0, + long seed1) { + return hashLen16(cityHash64(s, pos, len) - seed0, seed1); + } + + static long[] cityMurmur(byte[] s, int pos, int len, long seed0, + long seed1) { + + long a = seed0; + long b = seed1; + long c = 0; + long d = 0; + + int l = len - 16; + if (l <= 0) { + a = shiftMix(a * k1) * k1; + c = b * k1 + hashLen0to16(s, pos, len); + d = shiftMix(a + (len >= 8 ? fetch64(s, pos + 0) : c)); + } else { + + c = hashLen16(fetch64(s, pos + len - 8) + k1, a); + d = hashLen16(b + len, c + fetch64(s, pos + len - 16)); + a += d; + + do { + a ^= shiftMix(fetch64(s, pos + 0) * k1) * k1; + a *= k1; + b ^= a; + c ^= shiftMix(fetch64(s, pos + 8) * k1) * k1; + c *= k1; + d ^= c; + pos += 16; + l -= 16; + } while (l > 0); + } + + a = hashLen16(a, c); + b = hashLen16(d, b); + + return new long[]{a ^ b, hashLen16(b, a)}; + + } + + static long[] cityHash128WithSeed(byte[] s, int pos, int len, long seed0, + long seed1) { + + if (len < 128) { + return cityMurmur(s, pos, len, seed0, seed1); + } + + long[] v = new long[2], w = new long[2]; + long x = seed0; + long y = seed1; + long z = k1 * len; + + v[0] = rotate(y ^ k1, 49) * k1 + fetch64(s, pos); + v[1] = rotate(v[0], 42) * k1 + fetch64(s, pos + 8); + w[0] = rotate(y + z, 35) * k1 + x; + w[1] = rotate(x + fetch64(s, pos + 88), 53) * k1; + + do { + x = rotate(x + y + v[0] + fetch64(s, pos + 8), 37) * k1; + y = rotate(y + v[1] + fetch64(s, pos + 48), 42) * k1; + + x ^= w[1]; + y += v[0] + fetch64(s, pos + 40); + z = rotate(z + w[0], 33) * k1; + v = weakHashLen32WithSeeds(s, pos + 0, v[1] * k1, x + w[0]); + w = weakHashLen32WithSeeds(s, pos + 32, z + w[1], + y + fetch64(s, pos + 16)); + { + long swap = z; + z = x; + x = swap; + } + pos += 64; + x = rotate(x + y + v[0] + fetch64(s, pos + 8), 37) * k1; + y = rotate(y + v[1] + fetch64(s, pos + 48), 42) * k1; + x ^= w[1]; + y += v[0] + fetch64(s, pos + 40); + z = rotate(z + w[0], 33) * k1; + v = weakHashLen32WithSeeds(s, pos, v[1] * k1, x + w[0]); + w = weakHashLen32WithSeeds(s, pos + 32, z + w[1], + y + fetch64(s, pos + 16)); + { + long swap = z; + z = x; + x = swap; + } + pos += 64; + len -= 128; + } while (len >= 128); + + x += rotate(v[0] + z, 49) * k0; + z += rotate(w[0], 37) * k0; + + for (int tail_done = 0; tail_done < len;) { + tail_done += 32; + y = rotate(x + y, 42) * k0 + v[1]; + w[0] += fetch64(s, pos + len - tail_done + 16); + x = x * k0 + w[0]; + z += w[1] + fetch64(s, pos + len - tail_done); + w[1] += v[0]; + v = weakHashLen32WithSeeds(s, pos + len - tail_done, v[0] + z, + v[1]); + } + + x = hashLen16(x, v[0]); + y = hashLen16(y + z, w[0]); + + return new long[]{hashLen16(x + v[1], w[1]) + y, + hashLen16(x + w[1], y + v[1])}; + + } + + static long[] cityHash128(byte[] s, int pos, int len) { + + if (len >= 16) { + return cityHash128WithSeed(s, pos + 16, len - 16, + fetch64(s, pos + 0) ^ k3, fetch64(s, pos + 8)); + } else if (len >= 8) { + return cityHash128WithSeed(new byte[0], 0, 0, + fetch64(s, pos + 0) ^ (len * k0), + fetch64(s, pos + len - 8) ^ k1); + } else { + return cityHash128WithSeed(s, pos, len, k0, k1); + } + } +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ParsedSQLMetadata.java b/src/main/java/com/microsoft/sqlserver/jdbc/ParsedSQLMetadata.java index 19c34ebece..b1b8cb6d8c 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 0c4b2bef9c..9b39cad6dc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -29,6 +29,7 @@ import java.sql.Savepoint; import java.sql.Statement; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; @@ -122,42 +123,41 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial private Boolean isAzureDW = null; - static class Sha1HashKey implements java.io.Serializable { + static class CityHash128Key implements java.io.Serializable { /** * Always refresh SerialVersionUID when prompted */ private static final long serialVersionUID = 166788428640603097L; - private byte[] bytes; + String unhashedString; + private long[] segments; + private int hashCode; - Sha1HashKey(String sql, + CityHash128Key(String sql, String parametersDefinition) { - this(String.format("%s%s", sql, parametersDefinition)); + this(sql + parametersDefinition); } - Sha1HashKey(String s) { - bytes = getSha1Digest().digest(s.getBytes()); + CityHash128Key(String s) { + unhashedString = s; + 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) { - if (!(obj instanceof Sha1HashKey)) + if (!(obj instanceof CityHash128Key)) return false; - return java.util.Arrays.equals(bytes, ((Sha1HashKey) obj).bytes); + return (java.util.Arrays.equals(segments, ((CityHash128Key) obj).segments)//checks if hash is equal, short-circuitting; + && this.unhashedString.equals(((CityHash128Key) obj).unhashedString));//checks if string is equal } public int hashCode() { - return java.util.Arrays.hashCode(bytes); - } - - private java.security.MessageDigest getSha1Digest() { - try { - return java.security.MessageDigest.getInstance("SHA-1"); - } - catch (final java.security.NoSuchAlgorithmException e) { - // This is not theoretically possible, but we're forced to catch it anyway - throw new RuntimeException(e); + if (0 == hashCode) { + hashCode = java.util.Arrays.hashCode(segments); } + return hashCode; } } @@ -168,15 +168,12 @@ class PreparedStatementHandle { private int handle = 0; private final AtomicInteger handleRefCount = new AtomicInteger(); private boolean isDirectSql; - private volatile boolean evictedFromCache; - private volatile boolean explicitlyDiscarded; - private Sha1HashKey key; - - PreparedStatementHandle(Sha1HashKey key, - int handle, - boolean isDirectSql, - boolean isEvictedFromCache) { - this.key = key; + private volatile boolean evictedFromCache; + private volatile boolean explicitlyDiscarded; + private CityHash128Key key; + + PreparedStatementHandle(CityHash128Key key, int handle, boolean isDirectSql, boolean isEvictedFromCache) { + this.key = key; this.handle = handle; this.isDirectSql = isDirectSql; this.setIsEvictedFromCache(isEvictedFromCache); @@ -211,7 +208,7 @@ int getHandle() { } /** Get the cache key. */ - Sha1HashKey getKey() { + CityHash128Key getKey() { return key; } @@ -258,28 +255,29 @@ void removeReference() { static final private int PARSED_SQL_CACHE_SIZE = 100; /** Cache of parsed SQL meta data */ - static private ConcurrentLinkedHashMap parsedSQLCache; + static private ConcurrentLinkedHashMap parsedSQLCache; static { - parsedSQLCache = new Builder().maximumWeightedCapacity(PARSED_SQL_CACHE_SIZE).build(); + parsedSQLCache = new Builder() + .maximumWeightedCapacity(PARSED_SQL_CACHE_SIZE) + .build(); } /** Get prepared statement cache entry if exists, if not parse and create a new one */ - static ParsedSQLCacheItem getCachedParsedSQL(Sha1HashKey key) { + static ParsedSQLCacheItem getCachedParsedSQL(CityHash128Key key) { return parsedSQLCache.get(key); } /** Parse and create a information about parsed SQL text */ - static ParsedSQLCacheItem parseAndCacheSQL(Sha1HashKey key, - String sql) throws SQLServerException { + static ParsedSQLCacheItem parseAndCacheSQL(CityHash128Key key, String sql) throws SQLServerException { JDBCSyntaxTranslator translator = new JDBCSyntaxTranslator(); 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; } @@ -291,30 +289,31 @@ static ParsedSQLCacheItem parseAndCacheSQL(Sha1HashKey key, private int statementPoolingCacheSize = DEFAULT_STATEMENT_POOLING_CACHE_SIZE; /** Cache of prepared statement handles */ - private ConcurrentLinkedHashMap preparedStatementHandleCache; + private ConcurrentLinkedHashMap preparedStatementHandleCache; /** Cache of prepared statement parameter metadata */ - private ConcurrentLinkedHashMap parameterMetadataCache; + private ConcurrentLinkedHashMap parameterMetadataCache; /** * Checks whether statement pooling is enabled or disabled. The default is set to true; */ private boolean disableStatementPooling = true; - /** - * Find statement parameters. - * - * @param sql - * SQL text to parse for number of parameters to intialize. - */ - private static int countParams(String sql) { - int nParams = 0; + /** + * Locate statement parameters. + * + * @param sql + * SQL text to parse for positions of parameters to intialize. + */ + private static int[] locateParams(String sql) { + LinkedList parameterPositions = new LinkedList<>(); - // Figure out the expected number of parameters by counting the - // parameter placeholders in the SQL string. + // Locate the parameter placeholders in the SQL string. int offset = -1; - while ((offset = ParameterUtils.scanSQLForChar('?', sql, ++offset)) < sql.length()) - ++nParams; + while ((offset = ParameterUtils.scanSQLForChar('?', sql, ++offset)) < sql.length()) { + parameterPositions.add(offset); + } - return nParams; + // return as int[] + return parameterPositions.stream().mapToInt(Integer::valueOf).toArray(); } SqlFedAuthToken getAuthenticationResult() { @@ -5369,6 +5368,7 @@ protected void endRequestInternal() throws SQLException { static final char[] OUT = {' ', 'O', 'U', 'T'}; String replaceParameterMarkers(String sqlSrc, + int[] paramPositions, Parameter[] params, boolean isReturnValueSyntax) throws SQLServerException { final int MAX_PARAM_NAME_LEN = 6; @@ -5379,7 +5379,7 @@ String replaceParameterMarkers(String sqlSrc, int paramIndex = 0; while (true) { - int srcEnd = ParameterUtils.scanSQLForChar('?', sqlSrc, srcBegin); + int srcEnd = (paramIndex >= paramPositions.length) ? sqlSrc.length() : paramPositions[paramIndex]; sqlSrc.getChars(srcBegin, srcEnd, sqlDst, dstBegin); dstBegin += srcEnd - srcBegin; @@ -5797,43 +5797,40 @@ public void setStatementPoolingCacheSize(int value) { * @param value */ private void prepareCache() { - preparedStatementHandleCache = new Builder().maximumWeightedCapacity(getStatementPoolingCacheSize()) + preparedStatementHandleCache = new Builder().maximumWeightedCapacity(getStatementPoolingCacheSize()) .listener(new PreparedStatementCacheEvictionListener()).build(); - parameterMetadataCache = new Builder().maximumWeightedCapacity(getStatementPoolingCacheSize()) + parameterMetadataCache = new Builder().maximumWeightedCapacity(getStatementPoolingCacheSize()) .build(); } /** Get a parameter metadata cache entry if statement pooling is enabled */ - final SQLServerParameterMetaData getCachedParameterMetadata(Sha1HashKey key) { - if (!isStatementPoolingEnabled()) + final SQLServerParameterMetaData getCachedParameterMetadata(CityHash128Key key) { + if(!isStatementPoolingEnabled()) return null; return parameterMetadataCache.get(key); } /** Register a parameter metadata cache entry if statement pooling is enabled */ - final void registerCachedParameterMetadata(Sha1HashKey key, - SQLServerParameterMetaData pmd) { - if (!isStatementPoolingEnabled() || null == pmd) + final void registerCachedParameterMetadata(CityHash128Key key, SQLServerParameterMetaData pmd) { + if(!isStatementPoolingEnabled() || null == pmd) return; parameterMetadataCache.put(key, pmd); } /** Get or create prepared statement handle cache entry if statement pooling is enabled */ - final PreparedStatementHandle getCachedPreparedStatementHandle(Sha1HashKey key) { - if (!isStatementPoolingEnabled()) + final PreparedStatementHandle getCachedPreparedStatementHandle(CityHash128Key key) { + if(!isStatementPoolingEnabled()) return null; return preparedStatementHandleCache.get(key); } /** Get or create prepared statement handle cache entry if statement pooling is enabled */ - final PreparedStatementHandle registerCachedPreparedStatementHandle(Sha1HashKey key, - int handle, - boolean isDirectSql) { - if (!isStatementPoolingEnabled() || null == key) + final PreparedStatementHandle registerCachedPreparedStatementHandle(CityHash128Key key, int handle, boolean isDirectSql) { + if(!isStatementPoolingEnabled() || null == key) return null; PreparedStatementHandle cacheItem = new PreparedStatementHandle(key, handle, isDirectSql, false); @@ -5858,10 +5855,9 @@ final void evictCachedPreparedStatementHandle(PreparedStatementHandle handle) { } // Handle closing handles when removed from cache. - final class PreparedStatementCacheEvictionListener implements EvictionListener { - public void onEviction(Sha1HashKey key, - PreparedStatementHandle handle) { - if (null != handle) { + final class PreparedStatementCacheEvictionListener implements EvictionListener { + public void onEviction(CityHash128Key key, PreparedStatementHandle handle) { + if(null != handle) { handle.setIsEvictedFromCache(true); // Mark as evicted from cache. // Only discard if not referenced. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java index f9ebd3b1ca..f42110d782 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java @@ -609,6 +609,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 b1fc068ba0..33d6b13d62 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -33,7 +33,7 @@ import java.util.logging.Level; import com.microsoft.sqlserver.jdbc.SQLServerConnection.PreparedStatementHandle; -import com.microsoft.sqlserver.jdbc.SQLServerConnection.Sha1HashKey; +import com.microsoft.sqlserver.jdbc.SQLServerConnection.CityHash128Key; /** * SQLServerPreparedStatement provides JDBC prepared statement functionality. SQLServerPreparedStatement provides methods for the user to supply @@ -65,6 +65,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; @@ -75,7 +78,7 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS private PreparedStatementHandle cachedPreparedStatementHandle; /** Hash of user supplied SQL statement used for various cache lookups */ - private Sha1HashKey sqlTextCacheKey; + private CityHash128Key sqlTextCacheKey; /** * Array with parameter names generated in buildParamTypeDefinitions For mapping encryption information to parameters, as the second result set @@ -214,7 +217,7 @@ String getClassNameInternal() { stmtPoolable = true; // Create a cache key for this statement. - sqlTextCacheKey = new Sha1HashKey(sql); + sqlTextCacheKey = new CityHash128Key(sql); // Parse or fetch SQL metadata from cache. ParsedSQLCacheItem parsedSQL = getCachedParsedSQL(sqlTextCacheKey); @@ -231,7 +234,8 @@ String getClassNameInternal() { procedureName = parsedSQL.procedureName; bReturnValueSyntax = parsedSQL.bReturnValueSyntax; userSQL = parsedSQL.processedSQL; - initParams(parsedSQL.parameterCount); + userSQLParamPositions = parsedSQL.parameterPositions; + initParams(userSQLParamPositions.length); useBulkCopyForBatchInsert = conn.getUseBulkCopyForBatchInsert(); } @@ -371,7 +375,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; @@ -631,7 +635,7 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException { // Cache the reference to the newly created handle, NOT for cursorable handles. if (null == cachedPreparedStatementHandle && !isCursorable(executeMethod)) { cachedPreparedStatementHandle = connection.registerCachedPreparedStatementHandle( - new Sha1HashKey(preparedSQL, preparedTypeDefinitions), prepStmtHandle, executedSqlDirectly); + new CityHash128Key(preparedSQL, preparedTypeDefinitions), prepStmtHandle, executedSqlDirectly); } param.skipValue(tdsReader, true); @@ -978,7 +982,7 @@ private boolean reuseCachedHandle(boolean hasNewTypeDefinitions, // Check for new cache reference. if (null == cachedPreparedStatementHandle) { - PreparedStatementHandle cachedHandle = connection.getCachedPreparedStatementHandle(new Sha1HashKey(preparedSQL, preparedTypeDefinitions)); + PreparedStatementHandle cachedHandle = connection.getCachedPreparedStatementHandle(new CityHash128Key(preparedSQL, preparedTypeDefinitions)); // If handle was found then re-use, only if AE is not on and is not a batch query with new type definitions (We shouldn't reuse handle // if it is batch query and has new type definition, or if it is on, make sure encryptionMetadataIsRetrieved is retrieved. if (null != cachedHandle) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index cd56a7b84a..d34ba18e12 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -28,7 +28,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.microsoft.sqlserver.jdbc.SQLServerConnection.Sha1HashKey; +import com.microsoft.sqlserver.jdbc.SQLServerConnection.CityHash128Key; /** * SQLServerStatment provides the basic implementation of JDBC statement functionality. It also provides a number of base class implementation methods @@ -752,7 +752,7 @@ final void processResponse(TDSReader tdsReader) throws SQLServerException { private String ensureSQLSyntax(String sql) throws SQLServerException { if (sql.indexOf(LEFT_CURLY_BRACKET) >= 0) { - Sha1HashKey cacheKey = new Sha1HashKey(sql); + CityHash128Key cacheKey = new CityHash128Key(sql); // Check for cached SQL metadata. ParsedSQLCacheItem cacheItem = getCachedParsedSQL(cacheKey); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java index d2858efb15..13a5e735e5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java @@ -391,8 +391,8 @@ else if (ch == ':') if (null != name) { if (logger.isLoggable(Level.FINE)) { if (false == name.equals(SQLServerDriverStringProperty.USER.toString())) { - if (!name.toLowerCase(Locale.ENGLISH).contains("password") && - !name.toLowerCase(Locale.ENGLISH).contains("keystoresecret")) { + if (!name.toLowerCase(Locale.ENGLISH).contains("password") + && !name.toLowerCase(Locale.ENGLISH).contains("keystoresecret")) { logger.fine("Property:" + name + " Value:" + value); } else { @@ -559,7 +559,7 @@ static String escapeSQLId(String inID) { outID.append(']'); return outID.toString(); } - + /** * Checks if duplicate columns exists, in O(n) time. * @@ -570,7 +570,7 @@ static String escapeSQLId(String inID) { */ static void checkDuplicateColumnName(String columnName, Set columnNames) throws SQLServerException { - //columnList.add will return false if the same column name already exists + // columnList.add will return false if the same column name already exists if (!columnNames.add(columnName)) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_TVPDuplicateColumnName")); Object[] msgArgs = {columnName}; @@ -741,11 +741,11 @@ static final UUID readGUIDtoUUID(byte[] inputGUID) throws SQLServerException { long msb = 0L; for (int i = 0; i < 8; i++) { - msb = msb << 8 | ((long) inputGUID[i] & 0xFFL); + msb = msb << 8 | ((long) inputGUID[i] & 0xFFL); } long lsb = 0L; for (int i = 8; i < 16; i++) { - lsb = lsb << 8 | ((long) inputGUID[i] & 0xFFL); + lsb = lsb << 8 | ((long) inputGUID[i] & 0xFFL); } return new UUID(msb, lsb); } @@ -1000,7 +1000,7 @@ static synchronized boolean checkIfNeedNewAccessToken(SQLServerConnection connec use43Wrapper = supportJDBC43 && (9 <= jvmVersion); } - + // if driver is for JDBC 43 and jvm version is 9 or higher, then always return as SQLServerConnection43, // otherwise return SQLServerConnection static boolean use43Wrapper() {