Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance | Improved performance of column name string lookups #1066

Merged
merged 14 commits into from
Sep 24, 2019
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.UUID;


Expand All @@ -46,7 +47,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 @@ -696,7 +698,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 @@ -737,7 +739,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 @@ -772,7 +774,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 @@ -1247,7 +1249,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 @@ -1281,11 +1283,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);
parameterNames.put(p, columnIndex);
insensitiveParameterNames.put(p, columnIndex++);
}
}
} catch (SQLException e) {
SQLServerException.makeFromDriverError(connection, this, e.toString(), null, false);
Expand All @@ -1294,54 +1300,26 @@ private int findColumn(String columnName) throws SQLServerException {
}

int l = 0;
if (parameterNames != null)
if (null != parameterNames)
l = parameterNames.size();
if (l == 0)// Server didn't return anything, user might not have access
return 1;// attempting to look up the first column will return no access exception

// 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