diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java index 76eabac63..c773c9f80 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java @@ -24,8 +24,9 @@ import java.sql.Time; import java.sql.Timestamp; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; +import java.util.TreeMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -49,7 +50,8 @@ public class SQLServerCallableStatement extends SQLServerPreparedStatement imple private static final long serialVersionUID = 5044984771674532350L; /** the call param names */ - private ArrayList parameterNames; + private HashMap parameterNames; + private TreeMap insensitiveParameterNames; /** Number of registered OUT parameters */ int nOutParams = 0; @@ -702,7 +704,7 @@ public Object getObject(int index) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "getObject", index); checkClosed(); Object value = getValue(index, - getterGetParam(index).getJdbcTypeSetByUser() != null ? getterGetParam(index).getJdbcTypeSetByUser() + null != getterGetParam(index).getJdbcTypeSetByUser() ? getterGetParam(index).getJdbcTypeSetByUser() : getterGetParam(index).getJdbcType()); loggerExternal.exiting(getClassNameLogging(), "getObject", value); return value; @@ -743,7 +745,7 @@ public T getObject(int index, Class type) throws SQLException { } else if (type == UUID.class) { // read binary, avoid string allocation and parsing byte[] guid = getBytes(index); - returnValue = guid != null ? Util.readGUIDtoUUID(guid) : null; + returnValue = null != guid ? Util.readGUIDtoUUID(guid) : null; } else if (type == SQLXML.class) { returnValue = getSQLXML(index); } else if (type == Blob.class) { @@ -778,7 +780,7 @@ public Object getObject(String parameterName) throws SQLServerException { checkClosed(); int parameterIndex = findColumn(parameterName); Object value = getValue(parameterIndex, - getterGetParam(parameterIndex).getJdbcTypeSetByUser() != null ? getterGetParam(parameterIndex) + null != getterGetParam(parameterIndex).getJdbcTypeSetByUser() ? getterGetParam(parameterIndex) .getJdbcTypeSetByUser() : getterGetParam(parameterIndex).getJdbcType()); loggerExternal.exiting(getClassNameLogging(), "getObject", value); return value; @@ -1253,7 +1255,7 @@ public java.sql.Array getArray(String parameterName) throws SQLException { * @return the index */ private int findColumn(String columnName) throws SQLServerException { - if (parameterNames == null) { + if (null == parameterNames) { try (SQLServerStatement s = (SQLServerStatement) connection.createStatement()) { // Note we are concatenating the information from the passed in sql, not any arguments provided by the // user @@ -1287,11 +1289,15 @@ private int findColumn(String columnName) throws SQLServerException { SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "07009", false); } - ResultSet rs = s.executeQueryInternal(metaQuery.toString()); - parameterNames = new ArrayList<>(); - while (rs.next()) { - String parameterName = rs.getString(4); - parameterNames.add(parameterName.trim()); + try (ResultSet rs = s.executeQueryInternal(metaQuery.toString())) { + parameterNames = new HashMap<>(); + insensitiveParameterNames = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + int columnIndex = 0; + while (rs.next()) { + String p = rs.getString(4).trim(); + parameterNames.put(p, columnIndex); + insensitiveParameterNames.put(p, columnIndex++); + } } } catch (SQLException e) { SQLServerException.makeFromDriverError(connection, this, e.toString(), null, false); @@ -1310,12 +1316,7 @@ private int findColumn(String columnName) throws SQLServerException { // handle `@name` as well as `name`, since `@name` is what's returned // by DatabaseMetaData#getProcedureColumns - String columnNameWithoutAtSign = null; - if (columnName.startsWith("@")) { - columnNameWithoutAtSign = columnName.substring(1, columnName.length()); - } else { - columnNameWithoutAtSign = columnName; - } + String columnNameWithSign = columnName.startsWith("@") ? columnName : "@" + columnName; // In order to be as accurate as possible when locating parameter name // indexes, as well as be deterministic when running on various client @@ -1323,34 +1324,11 @@ private int findColumn(String columnName) throws SQLServerException { // 1. Search using case-sensitive non-locale specific (binary) compare first. // 2. Search using case-insensitive, non-locale specific (binary) compare last. - - int i; - int matchPos = -1; - // Search using case-sensitive, non-locale specific (binary) compare. - // If the user supplies a true match for the parameter name, we will find it here. - for (i = 0; i < l; i++) { - String sParam = parameterNames.get(i); - sParam = sParam.substring(1, sParam.length()); - if (sParam.equals(columnNameWithoutAtSign)) { - matchPos = i; - break; - } + Integer matchPos = parameterNames.get(columnNameWithSign); + if (null == matchPos) { + matchPos = insensitiveParameterNames.get(columnNameWithSign); } - - if (-1 == matchPos) { - // Check for case-insensitive match using a non-locale aware method. - // Use VM supplied String.equalsIgnoreCase to do the "case-insensitive search". - for (i = 0; i < l; i++) { - String sParam = parameterNames.get(i); - sParam = sParam.substring(1, sParam.length()); - if (sParam.equalsIgnoreCase(columnNameWithoutAtSign)) { - matchPos = i; - break; - } - } - } - - if (-1 == matchPos) { + if (null == matchPos) { MessageFormat form = new MessageFormat( SQLServerException.getErrString("R_parameterNotDefinedForProcedure")); Object[] msgArgs = {columnName, procedureName}; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index ca043a8bc..a6560b975 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -24,6 +24,8 @@ import java.sql.SQLXML; import java.text.MessageFormat; import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; @@ -153,6 +155,9 @@ final void setCurrentRowType(RowType rowType) { /** Flag set to true if the current row was updated through this ResultSet object */ private boolean updatedCurrentRow = false; + // Column name hash map for caching. + private final Map columnNames = new HashMap<>(); + final boolean getUpdatedCurrentRow() { return updatedCurrentRow; } @@ -632,17 +637,22 @@ public void close() throws SQLServerException { /** * Finds a column index given a column name. * - * @param columnName + * @param userProvidedColumnName * the name of the column * @throws SQLServerException * If any errors occur. * @return the column index */ @Override - public int findColumn(String columnName) throws SQLServerException { - loggerExternal.entering(getClassNameLogging(), "findColumn", columnName); + public int findColumn(String userProvidedColumnName) throws SQLServerException { + loggerExternal.entering(getClassNameLogging(), "findColumn", userProvidedColumnName); checkClosed(); + Integer value = columnNames.get(userProvidedColumnName); + if (null != value) { + return value; + } + // In order to be as accurate as possible when locating column name // indexes, as well as be deterministic when running on various client // locales, we search for column names using the following scheme: @@ -663,9 +673,9 @@ public int findColumn(String columnName) throws SQLServerException { // Search using case-sensitive, non-locale specific (binary) compare. // If the user supplies a true match for the column name, we will find it here. - int i; - for (i = 0; i < columns.length; i++) { - if (columns[i].getColumnName().equals(columnName)) { + for (int i = 0; i < columns.length; i++) { + if (columns[i].getColumnName().equals(userProvidedColumnName)) { + columnNames.put(userProvidedColumnName, i + 1); loggerExternal.exiting(getClassNameLogging(), "findColumn", i + 1); return i + 1; } @@ -675,14 +685,15 @@ public int findColumn(String columnName) throws SQLServerException { // Per JDBC spec, 27.3 "The driver will do a case-insensitive search for // columnName in it's attempt to map it to the column's index". // Use VM supplied String.equalsIgnoreCase to do the "case-insensitive search". - for (i = 0; i < columns.length; i++) { - if (columns[i].getColumnName().equalsIgnoreCase(columnName)) { + for (int i = 0; i < columns.length; i++) { + if (columns[i].getColumnName().equalsIgnoreCase(userProvidedColumnName)) { + columnNames.put(userProvidedColumnName, i + 1); loggerExternal.exiting(getClassNameLogging(), "findColumn", i + 1); return i + 1; } } MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumnName")); - Object[] msgArgs = {columnName}; + Object[] msgArgs = {userProvidedColumnName}; SQLServerException.makeFromDriverError(stmt.connection, stmt, form.format(msgArgs), "07009", false); return 0;