From: Date: May 28 2008 4:53pm Subject: Connector/J commit: r6775 - in branches/branch_5_1: . src/com/mysql/jdbc src/com/mysql/jdbc/jdbc2/optional src/testsuite/regression List-Archive: http://lists.mysql.com/commits/47149 X-Bug: 35610, 35150 Message-Id: <200805281453.m4SErPQx020995@bk-internal.mysql.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modified: branches/branch_5_1/CHANGES branches/branch_5_1/src/com/mysql/jdbc/ConnectionProperties.java branches/branch_5_1/src/com/mysql/jdbc/ConnectionPropertiesImpl.java branches/branch_5_1/src/com/mysql/jdbc/LocalizedErrorMessages.properties branches/branch_5_1/src/com/mysql/jdbc/ReplicationConnection.java branches/branch_5_1/src/com/mysql/jdbc/ResultSetImpl.java branches/branch_5_1/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java branches/branch_5_1/src/testsuite/regression/ResultSetRegressionTest.java Log: Fixed BUG#35610, BUG#35150- ResultSet.findColumn() and ResultSet.get...(String) doesn't allow column names to be used, and isn't congruent with ResultSetMetadata.getColumnName(). By default, we follow the JDBC Specification here, in that the 4.0 behavior is correct. Calling programs should use ResultSetMetaData.getColumnLabel() to dynamically determine the correct "name" to pass to ResultSet.findColumn() or ResultSet.get...(String) whether or not the query specifies an alias via "AS" for the column. ResultSetMetaData.getColumnName() will return the actual name of the column, if it exists, and this name can *not* be used as input to ResultSet.findColumn() or ResultSet.get...(String). The JDBC-3.0 (and earlier) specification has a bug, but you can get the buggy behavior (allowing column names *and* labels to be used for ResultSet.findColumn() and get...(String)) by setting "useColumnNamesInFindColumn" to "true". Modified: branches/branch_5_1/CHANGES =================================================================== --- branches/branch_5_1/CHANGES 2008-05-27 14:28:41 UTC (rev 6774) +++ branches/branch_5_1/CHANGES 2008-05-28 14:53:25 UTC (rev 6775) @@ -36,6 +36,20 @@ - Fixed BUG#36830 - DBMD.getColumns() doesn't return correct COLUMN_SIZE for SET columns. The logic wasn't accounting for the ","s in the column size. + - Fixed BUG#35610, BUG#35150- ResultSet.findColumn() and ResultSet.get...(String) doesn't allow + column names to be used, and isn't congruent with ResultSetMetadata.getColumnName(). + + By default, we follow the JDBC Specification here, in that the 4.0 behavior + is correct. Calling programs should use ResultSetMetaData.getColumnLabel() to dynamically determine + the correct "name" to pass to ResultSet.findColumn() or ResultSet.get...(String) whether or not the + query specifies an alias via "AS" for the column. ResultSetMetaData.getColumnName() will return the + actual name of the column, if it exists, and this name can *not* be used as input to ResultSet.findColumn() + or ResultSet.get...(String). + + The JDBC-3.0 (and earlier) specification has a bug, but you can get the buggy behavior + (allowing column names *and* labels to be used for ResultSet.findColumn() and get...(String)) by setting + "useColumnNamesInFindColumn" to "true". + 03-06-08 - Version 5.1.6 - JDBC-4.0-ized XAConnections and datasources. Modified: branches/branch_5_1/src/com/mysql/jdbc/ConnectionProperties.java =================================================================== --- branches/branch_5_1/src/com/mysql/jdbc/ConnectionProperties.java 2008-05-27 14:28:41 UTC (rev 6774) +++ branches/branch_5_1/src/com/mysql/jdbc/ConnectionProperties.java 2008-05-28 14:53:25 UTC (rev 6775) @@ -1594,4 +1594,8 @@ public int getSelfDestructOnPingMaxOperations(); public void setSelfDestructOnPingMaxOperations(int maxOperations); + + public boolean getUseColumnNamesInFindColumn(); + + public void setUseColumnNamesInFindColumn(boolean flag); } Modified: branches/branch_5_1/src/com/mysql/jdbc/ConnectionPropertiesImpl.java =================================================================== --- branches/branch_5_1/src/com/mysql/jdbc/ConnectionPropertiesImpl.java 2008-05-27 14:28:41 UTC (rev 6774) +++ branches/branch_5_1/src/com/mysql/jdbc/ConnectionPropertiesImpl.java 2008-05-28 14:53:25 UTC (rev 6775) @@ -1434,6 +1434,12 @@ Messages.getString("ConnectionProperties.useCompression"), //$NON-NLS-1$ "3.0.17", CONNECTION_AND_AUTH_CATEGORY, Integer.MIN_VALUE); //$NON-NLS-1$ + private BooleanConnectionProperty useColumnNamesInFindColumn = new BooleanConnectionProperty( + "useColumnNamesInFindColumn", + false, + Messages.getString("ConnectionProperties.useColumnNamesInFindColumn"), //$NON-NLS-1$ + "5.1.7", MISC_CATEGORY, Integer.MAX_VALUE); //$NON-NLS-1$ + private StringConnectionProperty useConfigs = new StringConnectionProperty( "useConfigs", //$NON-NLS-1$ null, @@ -4359,4 +4365,12 @@ public void setSelfDestructOnPingMaxOperations(int maxOperations) { this.selfDestructOnPingMaxOperations.setValue(maxOperations); } + + public boolean getUseColumnNamesInFindColumn() { + return this.useColumnNamesInFindColumn.getValueAsBoolean(); + } + + public void setUseColumnNamesInFindColumn(boolean flag) { + this.useColumnNamesInFindColumn.setValue(flag); + } } Modified: branches/branch_5_1/src/com/mysql/jdbc/LocalizedErrorMessages.properties =================================================================== --- branches/branch_5_1/src/com/mysql/jdbc/LocalizedErrorMessages.properties 2008-05-27 14:28:41 UTC (rev 6774) +++ branches/branch_5_1/src/com/mysql/jdbc/LocalizedErrorMessages.properties 2008-05-28 14:53:25 UTC (rev 6775) @@ -612,7 +612,7 @@ ConnectionProperties.utf8OutsideBmpExcludedColumnNamePattern=When "useBlobToStoreUTF8OutsideBMP" is set to "true", column names matching the given regex will still be treated as BLOBs unless they match the regex specified for "utf8OutsideBmpIncludedColumnNamePattern". The regex must follow the patterns used for the java.util.regex package. ConnectionProperties.utf8OutsideBmpIncludedColumnNamePattern=Used to specify exclusion rules to "utf8OutsideBmpExcludedColumnNamePattern". The regex must follow the patterns used for the java.util.regex package. ConnectionProperties.useLegacyDatetimeCode=Use code for DATE/TIME/DATETIME/TIMESTAMP handling in result sets and statements that consistently handles timezone conversions from client to server and back again, or use the legacy code for these datatypes that has been in the driver for backwards-compatibility? - +ConnectionProperties.useColumnNamesInFindColumn=Prior to JDBC-4.0, the JDBC specification had a bug related to what could be given as a "column name" to ResultSet methods like findColumn(), or getters that took a String property. JDBC-4.0 clarified "column name" to mean the label, as given in an "AS" clause and returned by ResultSetMetaData.getColumnLabel(), and if no AS clause, the column name. Setting this property to "true" will give behavior that is congruent to JDBC-3.0 and earlier versions of the JDBC specification, but which because of the specification bug could give unexpected results. This property is preferred over "useOldAliasMetadataBehavior" unless you need the specific behavior that it provides with respect to ResultSetMetadata. # # Error Messages for Connection Properties # Modified: branches/branch_5_1/src/com/mysql/jdbc/ReplicationConnection.java =================================================================== --- branches/branch_5_1/src/com/mysql/jdbc/ReplicationConnection.java 2008-05-27 14:28:41 UTC (rev 6774) +++ branches/branch_5_1/src/com/mysql/jdbc/ReplicationConnection.java 2008-05-28 14:53:25 UTC (rev 6775) @@ -2328,4 +2328,12 @@ public void setInGlobalTx(boolean flag) { this.currentConnection.setInGlobalTx(flag); } + + public boolean getUseColumnNamesInFindColumn() { + return this.currentConnection.getUseColumnNamesInFindColumn(); + } + + public void setUseColumnNamesInFindColumn(boolean flag) { + // TODO Auto-generated method stub + } } Modified: branches/branch_5_1/src/com/mysql/jdbc/ResultSetImpl.java =================================================================== --- branches/branch_5_1/src/com/mysql/jdbc/ResultSetImpl.java 2008-05-27 14:28:41 UTC (rev 6774) +++ branches/branch_5_1/src/com/mysql/jdbc/ResultSetImpl.java 2008-05-28 14:53:25 UTC (rev 6775) @@ -191,7 +191,7 @@ protected String catalog = null; /** Map column names (and all of their permutations) to column indices */ - protected Map columnNameToIndex = null; + protected Map columnLabelToIndex = null; /** Keep track of columns accessed */ protected boolean[] columnUsed = null; @@ -233,6 +233,8 @@ /** Map of fully-specified column names to column indices */ protected Map fullColumnNameToIndex = null; + protected Map columnNameToIndex = null; + protected boolean hasBuiltIndexMapping = false; /** @@ -326,6 +328,7 @@ private boolean jdbcCompliantTruncationForReads; private boolean useFastIntParsing = true; + private boolean useColumnNamesInFindColumn; protected final static char[] EMPTY_SPACE = new char[255]; @@ -482,6 +485,8 @@ } // else called by Connection.initializeResultsMetadataFromCache() when cached useLegacyDatetimeCode = this.connection.getUseLegacyDatetimeCode(); + this.useColumnNamesInFindColumn = this.connection.getUseColumnNamesInFindColumn(); + setRowPositionValidity(); } @@ -709,8 +714,9 @@ */ public void buildIndexMapping() throws SQLException { int numFields = this.fields.length; + this.columnLabelToIndex = new TreeMap(String.CASE_INSENSITIVE_ORDER); + this.fullColumnNameToIndex = new TreeMap(String.CASE_INSENSITIVE_ORDER); this.columnNameToIndex = new TreeMap(String.CASE_INSENSITIVE_ORDER); - this.fullColumnNameToIndex = new TreeMap(String.CASE_INSENSITIVE_ORDER); // We do this in reverse order, so that the 'first' column @@ -727,16 +733,21 @@ // for (int i = numFields - 1; i >= 0; i--) { Integer index = Constants.integerValueOf(i); - String columnName = this.fields[i].getName(); + String columnName = this.fields[i].getOriginalName(); + String columnLabel = this.fields[i].getName(); String fullColumnName = this.fields[i].getFullName(); - if (columnName != null) { - this.columnNameToIndex.put(columnName, index); + if (columnLabel != null) { + this.columnLabelToIndex.put(columnLabel, index); } if (fullColumnName != null) { this.fullColumnNameToIndex.put(fullColumnName, index); } + + if (columnName != null) { + this.columnNameToIndex.put(columnName, index); + } } // set the flag to prevent rebuilding... @@ -928,14 +939,14 @@ public void populateCachedMetaData(CachedResultSetMetaData cachedMetaData) throws SQLException { cachedMetaData.fields = this.fields; - cachedMetaData.columnNameToIndex = this.columnNameToIndex; + cachedMetaData.columnNameToIndex = this.columnLabelToIndex; cachedMetaData.fullColumnNameToIndex = this.fullColumnNameToIndex; cachedMetaData.metadata = getMetaData(); } public void initializeFromCachedMetaData(CachedResultSetMetaData cachedMetaData) { this.fields = cachedMetaData.fields; - this.columnNameToIndex = cachedMetaData.columnNameToIndex; + this.columnLabelToIndex = cachedMetaData.columnNameToIndex; this.fullColumnNameToIndex = cachedMetaData.fullColumnNameToIndex; this.hasBuiltIndexMapping = true; } @@ -1053,7 +1064,8 @@ // --------------------------------------------------------------------- - /** + /* + * [For JDBC-3.0 and older - http://java.sun.com/j2se/1.5.0/docs/api/java/sql/ResultSet.html#findColumn(java.lang.String)] * Map a ResultSet column name to a ResultSet column index * * @param columnName @@ -1063,6 +1075,16 @@ * * @exception SQLException * if a database access error occurs + * + * [For JDBC-4.0 and newer - http://java.sun.com/javase/6/docs/api/java/sql/ResultSet.html#findColumn(java.lang.String)] + * + * Maps the given ResultSet column label to its ResultSet column index. + * + * @param columnLabel + * the label for the column specified with the SQL AS clause. If the + * SQL AS clause was not specified, then the label is the name of the column + * + * @return the column index of the given column name */ public synchronized int findColumn(String columnName) throws SQLException { Integer index; @@ -1071,12 +1093,16 @@ buildIndexMapping(); } - index = (Integer) this.columnNameToIndex.get(columnName); + index = (Integer) this.columnLabelToIndex.get(columnName); + if (index == null && this.useColumnNamesInFindColumn) { + index = (Integer) this.columnNameToIndex.get(columnName); + } + if (index == null) { index = (Integer) this.fullColumnNameToIndex.get(columnName); } - + if (index != null) { return index.intValue() + 1; } @@ -7495,7 +7521,7 @@ this.rowData = null; this.defaultTimeZone = null; this.fields = null; - this.columnNameToIndex = null; + this.columnLabelToIndex = null; this.fullColumnNameToIndex = null; this.eventSink = null; this.warningChain = null; Modified: branches/branch_5_1/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java =================================================================== --- branches/branch_5_1/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java 2008-05-27 14:28:41 UTC (rev 6774) +++ branches/branch_5_1/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java 2008-05-28 14:53:25 UTC (rev 6775) @@ -28,6 +28,7 @@ import java.sql.SQLException; import java.sql.Savepoint; import java.sql.Statement; +import java.util.Map; import java.util.TimeZone; import com.mysql.jdbc.Connection; @@ -2538,4 +2539,12 @@ public void setSelfDestructOnPingSecondsLifetime(int seconds) { this.mc.setSelfDestructOnPingSecondsLifetime(seconds); } + + public boolean getUseColumnNamesInFindColumn() { + return this.mc.getUseColumnNamesInFindColumn(); + } + + public void setUseColumnNamesInFindColumn(boolean flag) { + this.mc.setUseColumnNamesInFindColumn(flag); + } } Modified: branches/branch_5_1/src/testsuite/regression/ResultSetRegressionTest.java =================================================================== --- branches/branch_5_1/src/testsuite/regression/ResultSetRegressionTest.java 2008-05-27 14:28:41 UTC (rev 6774) +++ branches/branch_5_1/src/testsuite/regression/ResultSetRegressionTest.java 2008-05-28 14:53:25 UTC (rev 6775) @@ -4681,4 +4681,79 @@ closeMemberJDBCResources(); } } + + /** + * Tests fix for BUG#35610, BUG#35150. We follow the JDBC Spec here, in that the 4.0 behavior + * is correct, the JDBC-3.0 (and earlier) spec has a bug, but you can get the buggy behavior + * (allowing column names *and* labels to be used) by setting "useColumnNamesInFindColumn" to + * "true". + * + * @throws Exception + */ + public void testBug35610() throws Exception { + createTable("testBug35610", "(field1 int, field2 int, field3 int)"); + this.stmt.executeUpdate("INSERT INTO testBug35610 VALUES (1, 2, 3)"); + exercise35610(this.stmt, false); + exercise35610(getConnectionWithProps("useColumnNamesInFindColumn=true").createStatement(), true); + } + + private void exercise35610(Statement configuredStmt, boolean force30Behavior) throws Exception { + try { + this.rs = configuredStmt.executeQuery("SELECT field1 AS f1, field2 AS f2, field3 FROM testBug35610"); + + ResultSetMetaData rsmd = this.rs.getMetaData(); + + assertEquals("field1", rsmd.getColumnName(1)); + assertEquals("field2", rsmd.getColumnName(2)); + assertEquals("f1", rsmd.getColumnLabel(1)); + assertEquals("f2", rsmd.getColumnLabel(2)); + + + + assertEquals("field3", rsmd.getColumnName(3)); + assertEquals("field3", rsmd.getColumnLabel(3)); + + this.rs.next(); + + // From ResultSet.html#getInt(java.lang.String) in JDBC-4.0 + // + // Retrieves the value of the designated column in the current row of this ResultSet + // object as an int in the Java programming language. + // + // Parameters: + // columnLabel - the label for the column specified with the SQL AS clause. If the + // SQL AS clause was not specified, then the label is the name of the column + // + + assertEquals(1, this.rs.getInt("f1")); + assertEquals(2, this.rs.getInt("f2")); + assertEquals(3, this.rs.getInt("field3")); + + // Pre-JDBC 4.0, some versions of the spec say "column name *or* label" + // for the column name argument... + + if (force30Behavior) { + assertEquals(1, this.rs.getInt("field1")); + assertEquals(2, this.rs.getInt("field2")); + } + + if (!force30Behavior) { + try { + this.rs.findColumn("field1"); + fail("findColumn(\"field1\" should have failed with an exception"); + } catch (SQLException sqlEx) { + // expected + } + + try { + this.rs.findColumn("field2"); + fail("findColumn(\"field2\" should have failed with an exception"); + } catch (SQLException sqlEx) { + // expected + } + } + } finally { + closeMemberJDBCResources(); + } + } }