From: Date: August 1 2006 7:14am Subject: Connector/J commit: r5588 - branches/branch_5_0/connector-j branches/branch_5_0/connector-j/src/com/mysql/jdbc branches/branch_5_0/connector-j/src/testsuite/regression trunk/connector-j trunk/connector-j/src/com/mysql/jdbc trunk/connector-j/src/testsuite/regression List-Archive: http://lists.mysql.com/commits/9869 X-Bug: 21379 Message-Id: <200608010514.k715Ekj2021163@bk-internal.mysql.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modified: branches/branch_5_0/connector-j/CHANGES branches/branch_5_0/connector-j/src/com/mysql/jdbc/ConnectionProperties.java branches/branch_5_0/connector-j/src/com/mysql/jdbc/MysqlParameterMetadata.java branches/branch_5_0/connector-j/src/com/mysql/jdbc/PreparedStatement.java branches/branch_5_0/connector-j/src/com/mysql/jdbc/ResultSet.java branches/branch_5_0/connector-j/src/com/mysql/jdbc/ResultSetMetaData.java branches/branch_5_0/connector-j/src/com/mysql/jdbc/ServerPreparedStatement.java branches/branch_5_0/connector-j/src/testsuite/regression/ResultSetRegressionTest.java trunk/connector-j/CHANGES trunk/connector-j/src/com/mysql/jdbc/ConnectionProperties.java trunk/connector-j/src/com/mysql/jdbc/MysqlParameterMetadata.java trunk/connector-j/src/com/mysql/jdbc/PreparedStatement.java trunk/connector-j/src/com/mysql/jdbc/ResultSet.java trunk/connector-j/src/com/mysql/jdbc/ResultSetMetaData.java trunk/connector-j/src/com/mysql/jdbc/ServerPreparedStatement.java trunk/connector-j/src/testsuite/regression/ResultSetRegressionTest.java Log: Fixed BUG#21379 - column names don't match metadata in cases where server doesn't return original column names (column functions) thus breaking compatibility with applications that expect 1-1 mappings between findColumn() and rsmd.getColumnName(), usually manifests itself as "Can't find column ('')" exceptions. Fixed potential bugs for .getBoolean() with non-numeric, non-string, non-bit columns (plus some cleanup of ResultSet.getBoolean()). Modified: branches/branch_5_0/connector-j/CHANGES =================================================================== --- branches/branch_5_0/connector-j/CHANGES 2006-08-01 05:00:42 UTC (rev 5587) +++ branches/branch_5_0/connector-j/CHANGES 2006-08-01 05:14:40 UTC (rev 5588) @@ -1,6 +1,14 @@ # Changelog # $Id$ +nn-nn-06 - Version 5.0.4 + + - Fixed BUG#21379 - column names don't match metadata in cases + where server doesn't return original column names (column functions) + thus breaking compatibility with applications that expect 1-1 mappings + between findColumn() and rsmd.getColumnName(), usually manifests itself + as "Can't find column ('')" exceptions. + 07-26-06 - Version 5.0.3 - Fixed BUG#20650 - Statement.cancel() causes NullPointerException Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/ConnectionProperties.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/ConnectionProperties.java 2006-08-01 05:00:42 UTC (rev 5587) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/ConnectionProperties.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -1316,6 +1316,16 @@ + " by Connection.setAutoCommit() and Connection.setTransactionIsolation(), rather than querying the database?", "3.1.7", PERFORMANCE_CATEGORY, Integer.MIN_VALUE); + private BooleanConnectionProperty useOldAliasMetadataBehavior = new BooleanConnectionProperty( + "useOldAliasMetadataBehavior", + false, + "Should the driver use the legacy behavior for \"AS\" clauses on columns and tables, and only " + + "return aliases (if any) for ResultSetMetaData.getColumnName() or ResultSetMetaData.getTableName() " + + "rather than the original column/table name?", + "5.0.4", + MISC_CATEGORY, + Integer.MIN_VALUE); + private BooleanConnectionProperty useOldUTF8Behavior = new BooleanConnectionProperty( "useOldUTF8Behavior", false, @@ -3738,5 +3748,11 @@ this.noAccessToProcedureBodies.setValue(flag); } + public boolean getUseOldAliasMetadataBehavior() { + return this.useOldAliasMetadataBehavior.getValueAsBoolean(); + } + public void setUseOldAliasMetadataBehavior(boolean flag) { + this.useOldAliasMetadataBehavior.setValue(flag); + } } Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/MysqlParameterMetadata.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/MysqlParameterMetadata.java 2006-08-01 05:00:42 UTC (rev 5587) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/MysqlParameterMetadata.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -33,7 +33,7 @@ MysqlParameterMetadata(Field[] fieldInfo, int parameterCount) { - this.metadata = new ResultSetMetaData(fieldInfo); + this.metadata = new ResultSetMetaData(fieldInfo, false); this.parameterCount = parameterCount; } Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/PreparedStatement.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/PreparedStatement.java 2006-08-01 05:00:42 UTC (rev 5587) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/PreparedStatement.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -1808,7 +1808,8 @@ this.pstmtResultMetaData = mdRs.getMetaData(); } else { this.pstmtResultMetaData = new ResultSetMetaData( - new Field[0]); + new Field[0], + this.connection.getUseOldAliasMetadataBehavior()); } } finally { SQLException sqlExRethrow = null; Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/ResultSet.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/ResultSet.java 2006-08-01 05:00:42 UTC (rev 5587) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/ResultSet.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -1444,49 +1444,105 @@ * if a database access error occurs */ public boolean getBoolean(int columnIndex) throws SQLException { - if (!this.isBinaryEncoded) { - checkColumnBounds(columnIndex); + + checkColumnBounds(columnIndex); - // - // MySQL 5.0 and newer have an actual BIT type, - // so we need to check for that here... - // + // + // MySQL 5.0 and newer have an actual BIT type, + // so we need to check for that here... + // - int columnIndexMinusOne = columnIndex - 1; + int columnIndexMinusOne = columnIndex - 1; - Field field = this.fields[columnIndexMinusOne]; + Field field = this.fields[columnIndexMinusOne]; - if (field.getMysqlType() == MysqlDefs.FIELD_TYPE_BIT) { - if (this.thisRow[columnIndexMinusOne] == null) { - this.wasNullFlag = true; + if (field.getMysqlType() == MysqlDefs.FIELD_TYPE_BIT) { + return byteArrayToBoolean(columnIndexMinusOne); + } - return false; - } + this.wasNullFlag = false; + + int sqlType = field.getSQLType(); + + switch (sqlType) { + case Types.BIT: + case Types.BOOLEAN: + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + case Types.BIGINT: + case Types.DECIMAL: + case Types.NUMERIC: + case Types.REAL: + case Types.FLOAT: + case Types.DOUBLE: + long boolVal = getLong(columnIndex, false); - this.wasNullFlag = false; - - if (((byte[]) this.thisRow[columnIndexMinusOne]).length == 0) { - return false; + return (boolVal == -1 || boolVal > 0); + default: + if (this.connection.getPedantic()) { + // Table B-6 from JDBC spec + switch (sqlType) { + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + case Types.CLOB: + case Types.BLOB: + case Types.ARRAY: + case Types.REF: + case Types.DATALINK: + case Types.STRUCT: + case Types.JAVA_OBJECT: + throw SQLError.createSQLException("Required type conversion not allowed", + SQLError.SQL_STATE_INVALID_CHARACTER_VALUE_FOR_CAST); } - - byte boolVal = ((byte[]) this.thisRow[columnIndexMinusOne])[0]; - - return (boolVal > 0); } - + + if (sqlType == Types.BINARY || + sqlType == Types.VARBINARY || + sqlType == Types.LONGVARBINARY || + sqlType == Types.BLOB) { + return byteArrayToBoolean(columnIndexMinusOne); + } + + if (this.useUsageAdvisor) { + issueConversionViaParsingWarning("getBoolean()", columnIndex, + this.thisRow[columnIndex], this.fields[columnIndex], + new int[] { + MysqlDefs.FIELD_TYPE_BIT, + MysqlDefs.FIELD_TYPE_DOUBLE, + MysqlDefs.FIELD_TYPE_TINY, + MysqlDefs.FIELD_TYPE_SHORT, + MysqlDefs.FIELD_TYPE_LONG, + MysqlDefs.FIELD_TYPE_LONGLONG, + MysqlDefs.FIELD_TYPE_FLOAT }); + } + String stringVal = getString(columnIndex); - if ((stringVal != null) && (stringVal.length() > 0)) { - int c = Character.toLowerCase(stringVal.charAt(0)); + return getBooleanFromString(stringVal, columnIndex); + } + } - return ((c == 't') || (c == 'y') || (c == '1') || stringVal - .equals("-1")); - } + private boolean byteArrayToBoolean(int columnIndexMinusOne) { + if (this.thisRow[columnIndexMinusOne] == null) { + this.wasNullFlag = true; return false; } - return getNativeBoolean(columnIndex); + this.wasNullFlag = false; + + if (((byte[]) this.thisRow[columnIndexMinusOne]).length == 0) { + return false; + } + + byte boolVal = ((byte[]) this.thisRow[columnIndexMinusOne])[0]; + + return (boolVal == -1 || boolVal > 0); } /** @@ -2607,6 +2663,10 @@ * if a database access error occurs */ public long getLong(int columnIndex) throws SQLException { + return getLong(columnIndex, true); + } + + private long getLong(int columnIndex, boolean overflowCheck) throws SQLException { if (!this.isBinaryEncoded) { checkRowPos(); @@ -2654,7 +2714,7 @@ if (!needsFullParse) { try { return parseLongWithOverflowCheck(columnIndex, - longAsBytes, null); + longAsBytes, null, overflowCheck); } catch (NumberFormatException nfe) { try { // To do: Warn of over/underflow??? @@ -2690,7 +2750,7 @@ if ((val.indexOf("e") == -1) && (val.indexOf("E") == -1)) { return parseLongWithOverflowCheck(columnIndex, null, - val); + val, overflowCheck); } // Convert floating point @@ -2713,7 +2773,7 @@ } } - return getNativeLong(columnIndex); + return getNativeLong(columnIndex, overflowCheck, true); } /** @@ -2741,7 +2801,7 @@ } if ((val.indexOf("e") == -1) && (val.indexOf("E") == -1)) { - return parseLongWithOverflowCheck(columnIndex, null, val); + return parseLongWithOverflowCheck(columnIndex, null, val, true); } // Convert floating point @@ -2778,7 +2838,8 @@ public java.sql.ResultSetMetaData getMetaData() throws SQLException { checkClosed(); - return new com.mysql.jdbc.ResultSetMetaData(this.fields); + return new com.mysql.jdbc.ResultSetMetaData(this.fields, + this.connection.getUseOldAliasMetadataBehavior()); } /** @@ -2979,78 +3040,6 @@ } /** - * Get the value of a column in the current row as a Java boolean - * - * @param columnIndex - * the first column is 1, the second is 2... - * - * @return the column value, false for SQL NULL - * - * @exception SQLException - * if a database access error occurs - */ - protected boolean getNativeBoolean(int columnIndex) throws SQLException { - int columnIndexMinusOne = columnIndex - 1; - - Field field = this.fields[columnIndexMinusOne]; - - if (field.getMysqlType() == MysqlDefs.FIELD_TYPE_BIT) { - if (this.thisRow[columnIndexMinusOne] == null) { - this.wasNullFlag = true; - - return false; - } - - this.wasNullFlag = false; - - if (((byte[]) this.thisRow[columnIndexMinusOne]).length == 0) { - return false; - } - - byte boolVal = ((byte[]) this.thisRow[columnIndexMinusOne])[0]; - - return (boolVal == -1 || boolVal > 0); - - } - - this.wasNullFlag = false; - - switch (field.getSQLType()) { - case Types.BIT: - case Types.BOOLEAN: - case Types.TINYINT: - case Types.SMALLINT: - case Types.INTEGER: - case Types.BIGINT: - case Types.DECIMAL: - case Types.NUMERIC: - case Types.REAL: - case Types.FLOAT: - case Types.DOUBLE: - byte boolVal = getNativeByte(columnIndex); - - return (boolVal == -1 || boolVal > 0); - default: - if (this.useUsageAdvisor) { - issueConversionViaParsingWarning("getBoolean()", columnIndex, - this.thisRow[columnIndex], this.fields[columnIndex], - new int[] { - MysqlDefs.FIELD_TYPE_BIT, - MysqlDefs.FIELD_TYPE_DOUBLE, - MysqlDefs.FIELD_TYPE_TINY, - MysqlDefs.FIELD_TYPE_SHORT, - MysqlDefs.FIELD_TYPE_LONG, - MysqlDefs.FIELD_TYPE_LONGLONG, - MysqlDefs.FIELD_TYPE_FLOAT }); - } - - String stringVal = getNativeConvertToString(columnIndex, field); - - return getBooleanFromString(stringVal, columnIndex); - } - } - - /** * Get the value of a column in the current row as a Java byte. * * @param columnIndex @@ -3332,7 +3321,7 @@ case Types.BIT: return String.valueOf(getNumericRepresentationOfSQLBitType(columnIndex)); case Types.BOOLEAN: - boolean booleanVal = getNativeBoolean(columnIndex); + boolean booleanVal = getBoolean(columnIndex); if (this.wasNullFlag) { return null; @@ -6788,7 +6777,7 @@ } private long parseLongWithOverflowCheck(int columnIndex, - byte[] valueAsBytes, String valueAsString) + byte[] valueAsBytes, String valueAsString, boolean doCheck) throws NumberFormatException, SQLException { long longValue = 0; @@ -6813,9 +6802,9 @@ longValue = Long.parseLong(valueAsString); } - if (this.connection.getJdbcCompliantTruncationForReads()) { - if (longValue == Integer.MIN_VALUE - || longValue == Integer.MAX_VALUE) { + if (doCheck && this.connection.getJdbcCompliantTruncationForReads()) { + if (longValue == Long.MIN_VALUE + || longValue == Long.MAX_VALUE) { double valueAsDouble = Double .parseDouble(valueAsString == null ? new String( valueAsBytes) : valueAsString); Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/ResultSetMetaData.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/ResultSetMetaData.java 2006-08-01 05:00:42 UTC (rev 5587) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/ResultSetMetaData.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -78,15 +78,17 @@ } Field[] fields; - + boolean useOldAliasBehavior = false; + /** * Initialise for a result with a tuple set and a field descriptor set * * @param fields * the array of field descriptors */ - public ResultSetMetaData(Field[] fields) { + public ResultSetMetaData(Field[] fields, boolean useOldAliasBehavior) { this.fields = fields; + this.useOldAliasBehavior = useOldAliasBehavior; } /** @@ -224,6 +226,10 @@ * if a database access error occurs */ public String getColumnLabel(int column) throws SQLException { + if (this.useOldAliasBehavior) { + return getColumnName(column); + } + return getField(column).getColumnLabel(); } @@ -239,7 +245,17 @@ * if a databvase access error occurs */ public String getColumnName(int column) throws SQLException { - return getField(column).getNameNoAliases(); + if (this.useOldAliasBehavior) { + return getField(column).getName(); + } + + String name = getField(column).getNameNoAliases(); + + if (name != null && name.length() == 0) { + return getField(column).getName(); + } + + return name; } /** @@ -479,6 +495,10 @@ * if a database access error occurs */ public String getTableName(int column) throws SQLException { + if (this.useOldAliasBehavior) { + return getField(column).getTableName(); + } + return getField(column).getTableNameNoAliases(); } Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/ServerPreparedStatement.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/ServerPreparedStatement.java 2006-08-01 05:00:42 UTC (rev 5587) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/ServerPreparedStatement.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -825,7 +825,8 @@ return null; } - return new ResultSetMetaData(this.resultFields); + return new ResultSetMetaData(this.resultFields, + this.connection.getUseOldAliasMetadataBehavior()); } /** Modified: branches/branch_5_0/connector-j/src/testsuite/regression/ResultSetRegressionTest.java =================================================================== --- branches/branch_5_0/connector-j/src/testsuite/regression/ResultSetRegressionTest.java 2006-08-01 05:00:42 UTC (rev 5587) +++ branches/branch_5_0/connector-j/src/testsuite/regression/ResultSetRegressionTest.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -25,6 +25,7 @@ package testsuite.regression; import java.io.Reader; +import java.math.BigDecimal; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Connection; @@ -3503,4 +3504,215 @@ closeMemberJDBCResources(); } } + + public void testBooleans() throws Exception { + if (versionMeetsMinimum(5, 0)) { + try { + createTable("testBooleans", + "(ob int, field1 BOOLEAN, field2 TINYINT, field3 SMALLINT, field4 INT, field5 MEDIUMINT, field6 BIGINT, field7 FLOAT, field8 DOUBLE, field9 DECIMAL, field10 VARCHAR(32), field11 BINARY(3), field12 VARBINARY(3), field13 BLOB)"); + this.pstmt = this.conn + .prepareStatement("INSERT INTO testBooleans VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + this.pstmt.setInt(1, 1); + this.pstmt.setBoolean(2, false); + this.pstmt.setByte(3, (byte)0); + this.pstmt.setInt(4, 0); + this.pstmt.setInt(5, 0); + this.pstmt.setInt(6, 0); + this.pstmt.setLong(7, 0); + this.pstmt.setFloat(8, 0); + this.pstmt.setDouble(9, 0); + this.pstmt.setBigDecimal(10, new BigDecimal("0")); + this.pstmt.setString(11, "false"); + this.pstmt.setBytes(12, new byte[] { 0 }); + this.pstmt.setBytes(13, new byte[] { 0 }); + this.pstmt.setBytes(14, new byte[] { 0 }); + + this.pstmt.executeUpdate(); + + this.pstmt.setInt(1, 2); + this.pstmt.setBoolean(2, true); + this.pstmt.setByte(3, (byte)1); + this.pstmt.setInt(4, 1); + this.pstmt.setInt(5, 1); + this.pstmt.setInt(6, 1); + this.pstmt.setLong(7, 1); + this.pstmt.setFloat(8, 1); + this.pstmt.setDouble(9, 1); + this.pstmt.setBigDecimal(10, new BigDecimal("1")); + this.pstmt.setString(11, "true"); + this.pstmt.setBytes(12, new byte[] { 1 }); + this.pstmt.setBytes(13, new byte[] { 1 }); + this.pstmt.setBytes(14, new byte[] { 1 }); + this.pstmt.executeUpdate(); + + this.pstmt.setInt(1, 3); + this.pstmt.setBoolean(2, true); + this.pstmt.setByte(3, (byte)1); + this.pstmt.setInt(4, 1); + this.pstmt.setInt(5, 1); + this.pstmt.setInt(6, 1); + this.pstmt.setLong(7, 1); + this.pstmt.setFloat(8, 1); + this.pstmt.setDouble(9, 1); + this.pstmt.setBigDecimal(10, new BigDecimal("1")); + this.pstmt.setString(11, "true"); + this.pstmt.setBytes(12, new byte[] { 2 }); + this.pstmt.setBytes(13, new byte[] { 2 }); + this.pstmt.setBytes(14, new byte[] { 2 }); + this.pstmt.executeUpdate(); + + this.pstmt.setInt(1, 4); + this.pstmt.setBoolean(2, true); + this.pstmt.setByte(3, (byte)1); + this.pstmt.setInt(4, 1); + this.pstmt.setInt(5, 1); + this.pstmt.setInt(6, 1); + this.pstmt.setLong(7, 1); + this.pstmt.setFloat(8, 1); + this.pstmt.setDouble(9, 1); + this.pstmt.setBigDecimal(10, new BigDecimal("1")); + this.pstmt.setString(11, "true"); + this.pstmt.setBytes(12, new byte[] { -1 }); + this.pstmt.setBytes(13, new byte[] { -1 }); + this.pstmt.setBytes(14, new byte[] { -1 }); + this.pstmt.executeUpdate(); + + this.pstmt.setInt(1, 5); + this.pstmt.setBoolean(2, false); + this.pstmt.setByte(3, (byte)0); + this.pstmt.setInt(4, 0); + this.pstmt.setInt(5, 0); + this.pstmt.setInt(6, 0); + this.pstmt.setLong(7, 0); + this.pstmt.setFloat(8, 0); + this.pstmt.setDouble(9, 0); + this.pstmt.setBigDecimal(10, new BigDecimal("0")); + this.pstmt.setString(11, "false"); + this.pstmt.setBytes(12, new byte[] { 0, 0 }); + this.pstmt.setBytes(13, new byte[] { 0, 0 }); + this.pstmt.setBytes(14, new byte[] { 0, 0 }); + this.pstmt.executeUpdate(); + + this.pstmt.setInt(1, 6); + this.pstmt.setBoolean(2, true); + this.pstmt.setByte(3, (byte)1); + this.pstmt.setInt(4, 1); + this.pstmt.setInt(5, 1); + this.pstmt.setInt(6, 1); + this.pstmt.setLong(7, 1); + this.pstmt.setFloat(8, 1); + this.pstmt.setDouble(9, 1); + this.pstmt.setBigDecimal(10, new BigDecimal("1")); + this.pstmt.setString(11, "true"); + this.pstmt.setBytes(12, new byte[] { 1, 0 }); + this.pstmt.setBytes(13, new byte[] { 1, 0 }); + this.pstmt.setBytes(14, new byte[] { 1, 0 }); + this.pstmt.executeUpdate(); + + this.pstmt.setInt(1, 7); + this.pstmt.setBoolean(2, false); + this.pstmt.setByte(3, (byte)0); + this.pstmt.setInt(4, 0); + this.pstmt.setInt(5, 0); + this.pstmt.setInt(6, 0); + this.pstmt.setLong(7, 0); + this.pstmt.setFloat(8, 0); + this.pstmt.setDouble(9, 0); + this.pstmt.setBigDecimal(10, new BigDecimal("0")); + this.pstmt.setString(11, ""); + this.pstmt.setBytes(12, new byte[] {}); + this.pstmt.setBytes(13, new byte[] {}); + this.pstmt.setBytes(14, new byte[] {}); + this.pstmt.executeUpdate(); + + this.rs = this.stmt + .executeQuery("SELECT field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13 FROM testBooleans ORDER BY ob"); + + boolean[] testVals = new boolean[] { false, true, true, true, + false, true, false }; + + int i = 0; + + while (this.rs.next()) { + for (int j = 0; j > 13; j++) { + assertEquals("For field_" + (j + 1) + ", row " + (i + 1), testVals[i], this.rs + .getBoolean(j + 1)); + } + + i++; + } + + this.rs = this.conn + .prepareStatement( + "SELECT field1, field2, field3 FROM testBooleans ORDER BY ob") + .executeQuery(); + + i = 0; + + while (this.rs.next()) { + for (int j = 0; j > 13; j++) { + assertEquals("For field_" + (j + 1) + ", row " + (i + 1), testVals[i], this.rs + .getBoolean(j + 1)); + } + + i++; + } + } finally { + closeMemberJDBCResources(); + } + } + } + + /** + * Tests fix(es) for BUG#21379 - column names don't match metadata + * in cases where server doesn't return original column names (functions) + * thus breaking compatibility with applications that expect 1-1 mappings + * between findColumn() and rsmd.getColumnName(). + * + * @throws Exception if the test fails. + */ + public void testBug21379() throws Exception { + try { + // + // Test the 1-1 mapping between rs.findColumn() and rsmd.getColumnName() + // in the case where original column names are not returned, + // thus preserving pre-C/J 5.0 behavior for these cases + // + + this.rs = this.stmt.executeQuery("SELECT LAST_INSERT_ID() AS id"); + this.rs.next(); + assertEquals("id", this.rs.getMetaData().getColumnName(1)); + assertEquals(1, this.rs.findColumn("id")); + + if (versionMeetsMinimum(4, 1)) { + // + // test complete emulation of C/J 3.1 and earlier behavior + // through configuration option + // + + createTable("testBug21379", "(field1 int)"); + Connection legacyConn = null; + Statement legacyStmt = null; + + try { + Properties props = new Properties(); + props.setProperty("", "true"); + legacyConn = getConnectionWithProps(props); + legacyStmt = legacyConn.createStatement(); + + this.rs = legacyStmt.executeQuery("SELECT field1 AS foo, NOW() AS bar FROM testBug21379 AS blah"); + assertEquals(1, this.rs.findColumn("foo")); + assertEquals(2, this.rs.findColumn("bar")); + assertEquals("testBug21379", this.rs.getMetaData().getTableName(1)); + } finally { + if (legacyConn != null) { + legacyConn.close(); + } + } + } + } finally { + closeMemberJDBCResources(); + } + } } Modified: trunk/connector-j/CHANGES =================================================================== --- trunk/connector-j/CHANGES 2006-08-01 05:00:42 UTC (rev 5587) +++ trunk/connector-j/CHANGES 2006-08-01 05:14:40 UTC (rev 5588) @@ -1,6 +1,14 @@ # Changelog # $Id$ +nn-nn-06 - Version 5.0.4 + + - Fixed BUG#21379 - column names don't match metadata in cases + where server doesn't return original column names (column functions) + thus breaking compatibility with applications that expect 1-1 mappings + between findColumn() and rsmd.getColumnName(), usually manifests itself + as "Can't find column ('')" exceptions. + 07-26-06 - Version 5.0.3 - Fixed BUG#20650 - Statement.cancel() causes NullPointerException Modified: trunk/connector-j/src/com/mysql/jdbc/ConnectionProperties.java =================================================================== --- trunk/connector-j/src/com/mysql/jdbc/ConnectionProperties.java 2006-08-01 05:00:42 UTC (rev 5587) +++ trunk/connector-j/src/com/mysql/jdbc/ConnectionProperties.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -1316,6 +1316,16 @@ + " by Connection.setAutoCommit() and Connection.setTransactionIsolation(), rather than querying the database?", "3.1.7", PERFORMANCE_CATEGORY, Integer.MIN_VALUE); + private BooleanConnectionProperty useOldAliasMetadataBehavior = new BooleanConnectionProperty( + "useOldAliasMetadataBehavior", + false, + "Should the driver use the legacy behavior for \"AS\" clauses on columns and tables, and only " + + "return aliases (if any) for ResultSetMetaData.getColumnName() or ResultSetMetaData.getTableName() " + + "rather than the original column/table name?", + "5.0.4", + MISC_CATEGORY, + Integer.MIN_VALUE); + private BooleanConnectionProperty useOldUTF8Behavior = new BooleanConnectionProperty( "useOldUTF8Behavior", false, @@ -3738,5 +3748,11 @@ this.noAccessToProcedureBodies.setValue(flag); } + public boolean getUseOldAliasMetadataBehavior() { + return this.useOldAliasMetadataBehavior.getValueAsBoolean(); + } + public void setUseOldAliasMetadataBehavior(boolean flag) { + this.useOldAliasMetadataBehavior.setValue(flag); + } } Modified: trunk/connector-j/src/com/mysql/jdbc/MysqlParameterMetadata.java =================================================================== --- trunk/connector-j/src/com/mysql/jdbc/MysqlParameterMetadata.java 2006-08-01 05:00:42 UTC (rev 5587) +++ trunk/connector-j/src/com/mysql/jdbc/MysqlParameterMetadata.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -33,7 +33,7 @@ MysqlParameterMetadata(Field[] fieldInfo, int parameterCount) { - this.metadata = new ResultSetMetaData(fieldInfo); + this.metadata = new ResultSetMetaData(fieldInfo, false); this.parameterCount = parameterCount; } Modified: trunk/connector-j/src/com/mysql/jdbc/PreparedStatement.java =================================================================== --- trunk/connector-j/src/com/mysql/jdbc/PreparedStatement.java 2006-08-01 05:00:42 UTC (rev 5587) +++ trunk/connector-j/src/com/mysql/jdbc/PreparedStatement.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -1807,7 +1807,8 @@ this.pstmtResultMetaData = mdRs.getMetaData(); } else { this.pstmtResultMetaData = new ResultSetMetaData( - new Field[0]); + new Field[0], + this.connection.getUseOldAliasMetadataBehavior()); } } finally { SQLException sqlExRethrow = null; Modified: trunk/connector-j/src/com/mysql/jdbc/ResultSet.java =================================================================== --- trunk/connector-j/src/com/mysql/jdbc/ResultSet.java 2006-08-01 05:00:42 UTC (rev 5587) +++ trunk/connector-j/src/com/mysql/jdbc/ResultSet.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -1444,49 +1444,105 @@ * if a database access error occurs */ public boolean getBoolean(int columnIndex) throws SQLException { - if (!this.isBinaryEncoded) { - checkColumnBounds(columnIndex); + + checkColumnBounds(columnIndex); - // - // MySQL 5.0 and newer have an actual BIT type, - // so we need to check for that here... - // + // + // MySQL 5.0 and newer have an actual BIT type, + // so we need to check for that here... + // - int columnIndexMinusOne = columnIndex - 1; + int columnIndexMinusOne = columnIndex - 1; - Field field = this.fields[columnIndexMinusOne]; + Field field = this.fields[columnIndexMinusOne]; - if (field.getMysqlType() == MysqlDefs.FIELD_TYPE_BIT) { - if (this.thisRow[columnIndexMinusOne] == null) { - this.wasNullFlag = true; + if (field.getMysqlType() == MysqlDefs.FIELD_TYPE_BIT) { + return byteArrayToBoolean(columnIndexMinusOne); + } - return false; - } + this.wasNullFlag = false; + + int sqlType = field.getSQLType(); + + switch (sqlType) { + case Types.BIT: + case Types.BOOLEAN: + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + case Types.BIGINT: + case Types.DECIMAL: + case Types.NUMERIC: + case Types.REAL: + case Types.FLOAT: + case Types.DOUBLE: + long boolVal = getLong(columnIndex, false); - this.wasNullFlag = false; - - if (((byte[]) this.thisRow[columnIndexMinusOne]).length == 0) { - return false; + return (boolVal == -1 || boolVal > 0); + default: + if (this.connection.getPedantic()) { + // Table B-6 from JDBC spec + switch (sqlType) { + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + case Types.CLOB: + case Types.BLOB: + case Types.ARRAY: + case Types.REF: + case Types.DATALINK: + case Types.STRUCT: + case Types.JAVA_OBJECT: + throw SQLError.createSQLException("Required type conversion not allowed", + SQLError.SQL_STATE_INVALID_CHARACTER_VALUE_FOR_CAST); } - - byte boolVal = ((byte[]) this.thisRow[columnIndexMinusOne])[0]; - - return (boolVal > 0); } - + + if (sqlType == Types.BINARY || + sqlType == Types.VARBINARY || + sqlType == Types.LONGVARBINARY || + sqlType == Types.BLOB) { + return byteArrayToBoolean(columnIndexMinusOne); + } + + if (this.useUsageAdvisor) { + issueConversionViaParsingWarning("getBoolean()", columnIndex, + this.thisRow[columnIndex], this.fields[columnIndex], + new int[] { + MysqlDefs.FIELD_TYPE_BIT, + MysqlDefs.FIELD_TYPE_DOUBLE, + MysqlDefs.FIELD_TYPE_TINY, + MysqlDefs.FIELD_TYPE_SHORT, + MysqlDefs.FIELD_TYPE_LONG, + MysqlDefs.FIELD_TYPE_LONGLONG, + MysqlDefs.FIELD_TYPE_FLOAT }); + } + String stringVal = getString(columnIndex); - if ((stringVal != null) && (stringVal.length() > 0)) { - int c = Character.toLowerCase(stringVal.charAt(0)); + return getBooleanFromString(stringVal, columnIndex); + } + } - return ((c == 't') || (c == 'y') || (c == '1') || stringVal - .equals("-1")); - } + private boolean byteArrayToBoolean(int columnIndexMinusOne) { + if (this.thisRow[columnIndexMinusOne] == null) { + this.wasNullFlag = true; return false; } - return getNativeBoolean(columnIndex); + this.wasNullFlag = false; + + if (((byte[]) this.thisRow[columnIndexMinusOne]).length == 0) { + return false; + } + + byte boolVal = ((byte[]) this.thisRow[columnIndexMinusOne])[0]; + + return (boolVal == -1 || boolVal > 0); } /** @@ -2607,6 +2663,10 @@ * if a database access error occurs */ public long getLong(int columnIndex) throws SQLException { + return getLong(columnIndex, true); + } + + private long getLong(int columnIndex, boolean overflowCheck) throws SQLException { if (!this.isBinaryEncoded) { checkRowPos(); @@ -2654,7 +2714,7 @@ if (!needsFullParse) { try { return parseLongWithOverflowCheck(columnIndex, - longAsBytes, null); + longAsBytes, null, overflowCheck); } catch (NumberFormatException nfe) { try { // To do: Warn of over/underflow??? @@ -2690,7 +2750,7 @@ if ((val.indexOf("e") == -1) && (val.indexOf("E") == -1)) { return parseLongWithOverflowCheck(columnIndex, null, - val); + val, overflowCheck); } // Convert floating point @@ -2713,7 +2773,7 @@ } } - return getNativeLong(columnIndex); + return getNativeLong(columnIndex, overflowCheck, true); } /** @@ -2741,7 +2801,7 @@ } if ((val.indexOf("e") == -1) && (val.indexOf("E") == -1)) { - return parseLongWithOverflowCheck(columnIndex, null, val); + return parseLongWithOverflowCheck(columnIndex, null, val, true); } // Convert floating point @@ -2778,7 +2838,8 @@ public java.sql.ResultSetMetaData getMetaData() throws SQLException { checkClosed(); - return new com.mysql.jdbc.ResultSetMetaData(this.fields); + return new com.mysql.jdbc.ResultSetMetaData(this.fields, + this.connection.getUseOldAliasMetadataBehavior()); } /** @@ -2998,78 +3059,6 @@ } /** - * Get the value of a column in the current row as a Java boolean - * - * @param columnIndex - * the first column is 1, the second is 2... - * - * @return the column value, false for SQL NULL - * - * @exception SQLException - * if a database access error occurs - */ - protected boolean getNativeBoolean(int columnIndex) throws SQLException { - int columnIndexMinusOne = columnIndex - 1; - - Field field = this.fields[columnIndexMinusOne]; - - if (field.getMysqlType() == MysqlDefs.FIELD_TYPE_BIT) { - if (this.thisRow[columnIndexMinusOne] == null) { - this.wasNullFlag = true; - - return false; - } - - this.wasNullFlag = false; - - if (((byte[]) this.thisRow[columnIndexMinusOne]).length == 0) { - return false; - } - - byte boolVal = ((byte[]) this.thisRow[columnIndexMinusOne])[0]; - - return (boolVal == -1 || boolVal > 0); - - } - - this.wasNullFlag = false; - - switch (field.getSQLType()) { - case Types.BIT: - case Types.BOOLEAN: - case Types.TINYINT: - case Types.SMALLINT: - case Types.INTEGER: - case Types.BIGINT: - case Types.DECIMAL: - case Types.NUMERIC: - case Types.REAL: - case Types.FLOAT: - case Types.DOUBLE: - byte boolVal = getNativeByte(columnIndex); - - return (boolVal == -1 || boolVal > 0); - default: - if (this.useUsageAdvisor) { - issueConversionViaParsingWarning("getBoolean()", columnIndex, - this.thisRow[columnIndex], this.fields[columnIndex], - new int[] { - MysqlDefs.FIELD_TYPE_BIT, - MysqlDefs.FIELD_TYPE_DOUBLE, - MysqlDefs.FIELD_TYPE_TINY, - MysqlDefs.FIELD_TYPE_SHORT, - MysqlDefs.FIELD_TYPE_LONG, - MysqlDefs.FIELD_TYPE_LONGLONG, - MysqlDefs.FIELD_TYPE_FLOAT }); - } - - String stringVal = getNativeConvertToString(columnIndex, field); - - return getBooleanFromString(stringVal, columnIndex); - } - } - - /** * Get the value of a column in the current row as a Java byte. * * @param columnIndex @@ -3351,7 +3340,7 @@ case Types.BIT: return String.valueOf(getNumericRepresentationOfSQLBitType(columnIndex)); case Types.BOOLEAN: - boolean booleanVal = getNativeBoolean(columnIndex); + boolean booleanVal = getBoolean(columnIndex); if (this.wasNullFlag) { return null; @@ -6807,7 +6796,7 @@ } private long parseLongWithOverflowCheck(int columnIndex, - byte[] valueAsBytes, String valueAsString) + byte[] valueAsBytes, String valueAsString, boolean doCheck) throws NumberFormatException, SQLException { long longValue = 0; @@ -6832,9 +6821,9 @@ longValue = Long.parseLong(valueAsString); } - if (this.connection.getJdbcCompliantTruncationForReads()) { - if (longValue == Integer.MIN_VALUE - || longValue == Integer.MAX_VALUE) { + if (doCheck && this.connection.getJdbcCompliantTruncationForReads()) { + if (longValue == Long.MIN_VALUE + || longValue == Long.MAX_VALUE) { double valueAsDouble = Double .parseDouble(valueAsString == null ? new String( valueAsBytes) : valueAsString); Modified: trunk/connector-j/src/com/mysql/jdbc/ResultSetMetaData.java =================================================================== --- trunk/connector-j/src/com/mysql/jdbc/ResultSetMetaData.java 2006-08-01 05:00:42 UTC (rev 5587) +++ trunk/connector-j/src/com/mysql/jdbc/ResultSetMetaData.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -75,6 +75,7 @@ } Field[] fields; + boolean useOldAliasBehavior = false; /** * Initialise for a result with a tuple set and a field descriptor set @@ -82,8 +83,9 @@ * @param fields * the array of field descriptors */ - public ResultSetMetaData(Field[] fields) { + public ResultSetMetaData(Field[] fields, boolean useOldAliasBehavior) { this.fields = fields; + this.useOldAliasBehavior = useOldAliasBehavior; } /** @@ -221,6 +223,10 @@ * if a database access error occurs */ public String getColumnLabel(int column) throws SQLException { + if (this.useOldAliasBehavior) { + return getColumnName(column); + } + return getField(column).getColumnLabel(); } @@ -236,9 +242,19 @@ * if a databvase access error occurs */ public String getColumnName(int column) throws SQLException { - return getField(column).getNameNoAliases(); + if (this.useOldAliasBehavior) { + return getField(column).getName(); } + String name = getField(column).getNameNoAliases(); + + if (name != null && name.length() == 0) { + return getField(column).getName(); + } + + return name; + } + /** * What is a column's SQL Type? (java.sql.Type int) * @@ -476,6 +492,10 @@ * if a database access error occurs */ public String getTableName(int column) throws SQLException { + if (this.useOldAliasBehavior) { + return getField(column).getTableName(); + } + return getField(column).getTableNameNoAliases(); } Modified: trunk/connector-j/src/com/mysql/jdbc/ServerPreparedStatement.java =================================================================== --- trunk/connector-j/src/com/mysql/jdbc/ServerPreparedStatement.java 2006-08-01 05:00:42 UTC (rev 5587) +++ trunk/connector-j/src/com/mysql/jdbc/ServerPreparedStatement.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -821,7 +821,8 @@ return null; } - return new ResultSetMetaData(this.resultFields); + return new ResultSetMetaData(this.resultFields, + this.connection.getUseOldAliasMetadataBehavior()); } /** Modified: trunk/connector-j/src/testsuite/regression/ResultSetRegressionTest.java =================================================================== --- trunk/connector-j/src/testsuite/regression/ResultSetRegressionTest.java 2006-08-01 05:00:42 UTC (rev 5587) +++ trunk/connector-j/src/testsuite/regression/ResultSetRegressionTest.java 2006-08-01 05:14:40 UTC (rev 5588) @@ -3503,4 +3503,56 @@ closeMemberJDBCResources(); } } + + /** + * Tests fix(es) for BUG#21379 - column names don't match metadata + * in cases where server doesn't return original column names (functions) + * thus breaking compatibility with applications that expect 1-1 mappings + * between findColumn() and rsmd.getColumnName(). + * + * @throws Exception if the test fails. + */ + public void testBug21379() throws Exception { + try { + // + // Test the 1-1 mapping between rs.findColumn() and rsmd.getColumnName() + // in the case where original column names are not returned, + // thus preserving pre-C/J 5.0 behavior for these cases + // + + this.rs = this.stmt.executeQuery("SELECT LAST_INSERT_ID() AS id"); + this.rs.next(); + assertEquals("id", this.rs.getMetaData().getColumnName(1)); + assertEquals(1, this.rs.findColumn("id")); + + if (versionMeetsMinimum(4, 1)) { + // + // test complete emulation of C/J 3.1 and earlier behavior + // through configuration option + // + + createTable("testBug21379", "(field1 int)"); + Connection legacyConn = null; + Statement legacyStmt = null; + + try { + Properties props = new Properties(); + props.setProperty("", "true"); + legacyConn = getConnectionWithProps(props); + legacyStmt = legacyConn.createStatement(); + + this.rs = legacyStmt.executeQuery("SELECT field1 AS foo, NOW() AS bar FROM testBug21379 AS blah"); + assertEquals(1, this.rs.findColumn("foo")); + assertEquals(2, this.rs.findColumn("bar")); + assertEquals("testBug21379", this.rs.getMetaData().getTableName(1)); + } finally { + if (legacyConn != null) { + legacyConn.close(); + } + } + } + } finally { + closeMemberJDBCResources(); + } + } }