Skip to content

Commit

Permalink
Performance | Improved performance of column name string lookups (#1066)
Browse files Browse the repository at this point in the history
* Fix | Made column name lookup more performant

* Fix | Spacing

* Fix | Changed arraylist to maps

* Fix | Add newline

* Add | Caching for previously retrieved columns

* Fix | Formatting

* Fix | Variable issue

* Fix | null order

* Fix | part 2

* Revert | Integer changes

* Fix | Trim retrieved column name

* Fix | Clear column names whenever ResultSet cursor is moved

* Revert "Fix | Clear column names whenever ResultSet cursor is moved"

This reverts commit db42d44.
  • Loading branch information
rene-ye authored Sep 24, 2019
1 parent 122f55a commit 2c5f99d
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -49,7 +50,8 @@ public class SQLServerCallableStatement extends SQLServerPreparedStatement imple
private static final long serialVersionUID = 5044984771674532350L;

/** the call param names */
private ArrayList<String> parameterNames;
private HashMap<String, Integer> parameterNames;
private TreeMap<String, Integer> insensitiveParameterNames;

/** Number of registered OUT parameters */
int nOutParams = 0;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -743,7 +745,7 @@ public <T> T getObject(int index, Class<T> 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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -1310,47 +1316,19 @@ 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
// locales, we search for parameter names using the following scheme:

// 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};
Expand Down
29 changes: 20 additions & 9 deletions src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, Integer> columnNames = new HashMap<>();

final boolean getUpdatedCurrentRow() {
return updatedCurrentRow;
}
Expand Down Expand Up @@ -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:
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down

0 comments on commit 2c5f99d

Please sign in to comment.