diff --git a/.gitignore b/.gitignore index fad40fcb3..acb9b8d91 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ local.properties .classpath .vscode/ .settings/ +.gradle/ .loadpath # External tool builders diff --git a/build.gradle b/build.gradle index 55792f0a3..38e407d03 100644 --- a/build.gradle +++ b/build.gradle @@ -67,8 +67,9 @@ repositories { dependencies { compile 'com.microsoft.azure:azure-keyvault:0.9.7', - 'com.microsoft.azure:adal4j:1.1.3' - + 'com.microsoft.azure:adal4j:1.1.3', + 'com.google.guava:guava:19.0' + testCompile 'junit:junit:4.12', 'org.junit.platform:junit-platform-console:1.0.0-M3', 'org.junit.platform:junit-platform-commons:1.0.0-M3', diff --git a/pom.xml b/pom.xml index 8dfd2d078..dfcc503e0 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,13 @@ true + + com.google.guava + guava + 19.0 + false + + junit diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index b1dff7c4d..923914f4e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -28,6 +28,9 @@ import java.util.Vector; import java.util.logging.Level; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.Cache; + /** * SQLServerPreparedStatement provides JDBC prepared statement functionality. SQLServerPreparedStatement provides methods for the user to supply * parameters as any native Java type and many Java object types. @@ -98,6 +101,48 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS */ private boolean encryptionMetadataIsRetrieved = false; + /** Size of the prepared statement meta data cache */ + static final private int preparedStatementMetadataSQLCacheSize = 100; + + /** Cache of prepared statement meta data */ + static private Cache preparedStatementSQLMetadataCache; + static { + preparedStatementSQLMetadataCache = CacheBuilder.newBuilder() + .maximumSize(preparedStatementMetadataSQLCacheSize) + .build(); + } + + /** + * Used to keep track of an individual handle ready for un-prepare. + */ + private final class PreparedStatementMetadataSQLCacheItem { + String preparedSQLText; + int parameterCount; + String procedureName; + boolean bReturnValueSyntax; + + PreparedStatementMetadataSQLCacheItem(String preparedSQLText, int parameterCount, String procedureName, boolean bReturnValueSyntax){ + this.preparedSQLText = preparedSQLText; + this.parameterCount = parameterCount; + this.procedureName = procedureName; + this.bReturnValueSyntax = bReturnValueSyntax; + } + } + + /** Get prepared statement cache entry if exists */ + public PreparedStatementMetadataSQLCacheItem getCachedPreparedStatementSQLMetadata(String initialSql){ + return preparedStatementSQLMetadataCache.getIfPresent(initialSql); + } + + /** Cache entry for this prepared statement */ + public PreparedStatementMetadataSQLCacheItem metadataSQLCacheItem; + + /** Add cache entry for prepared statement metadata*/ + public void cachePreparedStatementSQLMetaData(String initialSql, PreparedStatementMetadataSQLCacheItem newItem){ + + preparedStatementSQLMetadataCache.put(initialSql, newItem); + } + // Internal function used in tracing String getClassNameInternal() { return "SQLServerPreparedStatement"; @@ -128,13 +173,34 @@ String getClassNameInternal() { stmtPoolable = true; sqlCommand = sql; - JDBCSyntaxTranslator translator = new JDBCSyntaxTranslator(); - sql = translator.translate(sql); - procedureName = translator.getProcedureName(); // may return null - bReturnValueSyntax = translator.hasReturnValueSyntax(); - - userSQL = sql; - initParams(userSQL); + // Save original SQL statement. + sqlCommand = sql; + + // Check for cached SQL metadata. + PreparedStatementMetadataSQLCacheItem cacheItem = getCachedPreparedStatementSQLMetadata(sql); + + // No cached meta data found, parse. + if(null == cacheItem) { + JDBCSyntaxTranslator translator = new JDBCSyntaxTranslator(); + + userSQL = translator.translate(sql); + procedureName = translator.getProcedureName(); // may return null + bReturnValueSyntax = translator.hasReturnValueSyntax(); + + // Save processed SQL statement. + initParams(userSQL); + + // Cache this entry. + cacheItem = new PreparedStatementMetadataSQLCacheItem(userSQL, inOutParam.length, procedureName, bReturnValueSyntax); + cachePreparedStatementSQLMetaData(sqlCommand/*original command as key*/, cacheItem); + } + else { + // Retrieve from cache item. + procedureName = cacheItem.procedureName; + bReturnValueSyntax = cacheItem.bReturnValueSyntax; + userSQL = cacheItem.preparedSQLText; + initParams(cacheItem.parameterCount); + } } /** @@ -217,12 +283,11 @@ final void closeInternal() { } /** - * Intialize the statement parameters. + * Find and intialize the statement parameters. * * @param sql */ /* L0 */ final void initParams(String sql) { - encryptionMetadataIsRetrieved = false; int nParams = 0; // Figure out the expected number of parameters by counting the @@ -231,6 +296,15 @@ final void closeInternal() { while ((offset = ParameterUtils.scanSQLForChar('?', sql, ++offset)) < sql.length()) ++nParams; + initParams(nParams); + } + + /** + * Intialize the statement parameters. + * + * @param sql + */ + /* L0 */ final void initParams(int nParams) { inOutParam = new Parameter[nParams]; for (int i = 0; i < nParams; i++) { inOutParam[i] = new Parameter(Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, connection));