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 @@ -1282,10 +1284,13 @@ private int findColumn(String columnName) throws SQLServerException {
}

ResultSet rs = s.executeQueryInternal(metaQuery.toString());
parameterNames = new ArrayList<>();
parameterNames = new HashMap<>();
insensitiveParameterNames = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
int columnIndex = 0;
while (rs.next()) {
String parameterName = rs.getString(4);
parameterNames.add(parameterName.trim());
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 @@ -1301,47 +1306,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;
}
}

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;
}
}
// 2. Search using case-insensitive, non-locale specific (binary) compare last.
Integer matchPos = parameterNames.get(columnNameWithSign);
if (null == matchPos) {
matchPos = insensitiveParameterNames.get(columnNameWithSign);
}

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