From 68a6f48da53dd0ad2e20b450a41ca600b8c1e1d2 Mon Sep 17 00:00:00 2001 From: Cheng Pan Date: Fri, 29 Nov 2024 11:48:39 +0800 Subject: [PATCH] [KYUUBI #6828] Clean up and improve error message for KyuubiBaseResultSet ### Why are the changes needed? Backport https://github.com/apache/hive/commit/2b0e424daa67bfa62e695701533fb80b8f10f383 The commit does not have a Hive ticket. ### How was this patch tested? Pass GHA to ensure it breaks nothing, and verify locally that the reported error message does contain the data when failing to parse the illegal date. ![image](https://github.com/user-attachments/assets/ba210160-c9cb-4539-8b28-0f445b6ce9a5) ### Was this patch authored or co-authored using generative AI tooling? No. Closes #6828 from pan3793/hive-jdbc-2b0e424. Closes #6828 b6e03a661 [Cheng Pan] KyuubiSQLException 63861af99 [Cheng Pan] Backport Hive 2b0e424 - Clean up and improve error message for KyuubiBaseResultSet Authored-by: Cheng Pan Signed-off-by: Cheng Pan --- .../kyuubi/jdbc/hive/KyuubiBaseResultSet.java | 285 +++++++++++------- 1 file changed, 176 insertions(+), 109 deletions(-) diff --git a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiBaseResultSet.java b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiBaseResultSet.java index cf47e104295..d9576a2986d 100644 --- a/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiBaseResultSet.java +++ b/kyuubi-hive-jdbc/src/main/java/org/apache/kyuubi/jdbc/hive/KyuubiBaseResultSet.java @@ -33,7 +33,6 @@ import org.apache.kyuubi.shaded.hive.service.rpc.thrift.TTypeId; /** Data independent base class which implements the common part of all Kyuubi result sets. */ -@SuppressWarnings("deprecation") public abstract class KyuubiBaseResultSet implements SQLResultSet { protected Statement statement = null; @@ -50,32 +49,29 @@ public abstract class KyuubiBaseResultSet implements SQLResultSet { @Override public int findColumn(String columnName) throws SQLException { int columnIndex = 0; - boolean findColumn = false; - for (String normalizedColumnName : normalizedColumnNames) { - ++columnIndex; - String[] names = normalizedColumnName.split("\\."); - String name = names[names.length - 1]; - if (name.equalsIgnoreCase(columnName) || normalizedColumnName.equalsIgnoreCase(columnName)) { - findColumn = true; - break; + if (columnName != null) { + final String lcColumnName = columnName.toLowerCase(); + for (final String normalizedColumnName : normalizedColumnNames) { + ++columnIndex; + final int idx = normalizedColumnName.lastIndexOf('.'); + final String name = + (idx == -1) ? normalizedColumnName : normalizedColumnName.substring(1 + idx); + if (name.equals(lcColumnName) || normalizedColumnName.equals(lcColumnName)) { + return columnIndex; + } } } - if (!findColumn) { - throw new KyuubiSQLException("Could not find " + columnName + " in " + normalizedColumnNames); - } else { - return columnIndex; - } + throw new KyuubiSQLException("Could not find " + columnName + " in " + normalizedColumnNames); } @Override public BigDecimal getBigDecimal(int columnIndex) throws SQLException { - Object val = getObject(columnIndex); - - if (val == null || val instanceof BigDecimal) { + final Object val = getObject(columnIndex); + if (val instanceof BigDecimal) { return (BigDecimal) val; } - - throw new KyuubiSQLException("Illegal conversion"); + throw new KyuubiSQLException( + "Illegal conversion to BigDecimal from column " + columnIndex + " [" + val + "]"); } @Override @@ -96,19 +92,23 @@ public BigDecimal getBigDecimal(String columnName, int scale) throws SQLExceptio @Override public InputStream getBinaryStream(int columnIndex) throws SQLException { - Object obj = getObject(columnIndex); + final Object obj = getObject(columnIndex); if (obj == null) { return null; - } else if (obj instanceof InputStream) { + } + if (obj instanceof InputStream) { return (InputStream) obj; - } else if (obj instanceof byte[]) { + } + if (obj instanceof byte[]) { byte[] byteArray = (byte[]) obj; return new ByteArrayInputStream(byteArray); - } else if (obj instanceof String) { - String str = (String) obj; + } + if (obj instanceof String) { + final String str = (String) obj; return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)); } - throw new KyuubiSQLException("Illegal conversion to binary stream from column " + columnIndex); + throw new KyuubiSQLException( + "Illegal conversion to binary stream from column " + columnIndex + " [" + obj + "]"); } @Override @@ -116,35 +116,76 @@ public InputStream getBinaryStream(String columnName) throws SQLException { return getBinaryStream(findColumn(columnName)); } + /** + * Retrieves the value of the designated column in the current row of this ResultSet object as a + * boolean in the Java programming language. If the designated column has a datatype of CHAR or + * VARCHAR and contains a "0" or has a datatype of BIT, TINYINT, SMALLINT, INTEGER or BIGINT and + * contains a 0, a value of false is returned. + * + *

For a true value, this implementation relaxes the JDBC specs. If the designated column has a + * datatype of CHAR or VARCHAR and contains a values other than "0" or has a datatype of BIT, + * TINYINT, SMALLINT, INTEGER or BIGINT and contains a value other than 0, a value of true is + * returned. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the value returned is false + * @throws if the columnIndex is not valid; if a database access error occurs or this method is + * called on a closed result set + * @see ResultSet#getBoolean(int) + */ @Override public boolean getBoolean(int columnIndex) throws SQLException { - Object obj = getObject(columnIndex); + final Object obj = getObject(columnIndex); + if (obj == null) { + return false; + } if (obj instanceof Boolean) { return (Boolean) obj; - } else if (obj == null) { - return false; - } else if (obj instanceof Number) { + } + if (obj instanceof Number) { return ((Number) obj).intValue() != 0; - } else if (obj instanceof String) { - return !obj.equals("0"); } - throw new KyuubiSQLException("Cannot convert column " + columnIndex + " to boolean"); + if (obj instanceof String) { + return !"0".equals(obj); + } + throw new KyuubiSQLException( + "Illegal conversion to boolean from column " + columnIndex + " [" + obj + "]"); } + /** + * Retrieves the value of the designated column in the current row of this ResultSet object as a + * boolean in the Java programming language. If the designated column has a datatype of CHAR or + * VARCHAR and contains a "0" or has a datatype of BIT, TINYINT, SMALLINT, INTEGER or BIGINT and + * contains a 0, a value of false is returned. + * + *

For a true value, this implementation relaxes the JDBC specs. If the designated column has a + * datatype of CHAR or VARCHAR and contains a values other than "0" or has a datatype of BIT, + * TINYINT, SMALLINT, INTEGER or BIGINT and contains a value other than 0, a value of true is + * returned. + * + * @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 value; if the value is SQL NULL, the value returned is false + * @throws if the columnIndex is not valid; if a database access error occurs or this method is + * called on a closed result set + * @see ResultSet#getBoolean(String) + */ @Override - public boolean getBoolean(String columnName) throws SQLException { - return getBoolean(findColumn(columnName)); + public boolean getBoolean(String columnLabel) throws SQLException { + return getBoolean(findColumn(columnLabel)); } @Override public byte getByte(int columnIndex) throws SQLException { - Object obj = getObject(columnIndex); + final Object obj = getObject(columnIndex); + if (obj == null) { + return 0; + } if (obj instanceof Number) { return ((Number) obj).byteValue(); - } else if (obj == null) { - return 0; } - throw new KyuubiSQLException("Cannot convert column " + columnIndex + " to byte"); + throw new KyuubiSQLException( + "Illegal conversion to byte from column " + columnIndex + " [" + obj + "]"); } @Override @@ -159,23 +200,23 @@ public int getConcurrency() throws SQLException { @Override public Date getDate(int columnIndex) throws SQLException { - Object obj = getObject(columnIndex); + final Object obj = getObject(columnIndex); if (obj == null) { return null; } if (obj instanceof Date) { return (Date) obj; } - try { - if (obj instanceof String) { + if (obj instanceof String) { + try { return Date.valueOf((String) obj); + } catch (Exception e) { + throw new KyuubiSQLException( + "Illegal conversion to Date from column " + columnIndex + " [" + obj + "]", e); } - } catch (Exception e) { - throw new KyuubiSQLException("Cannot convert column " + columnIndex + " to date: " + e, e); } - // If we fell through to here this is not a valid type conversion throw new KyuubiSQLException( - "Cannot convert column " + columnIndex + " to date: Illegal conversion"); + "Illegal conversion to Date from column " + columnIndex + " [" + obj + "]"); } @Override @@ -211,19 +252,23 @@ private Date parseDate(Date value, Calendar cal) { @Override public double getDouble(int columnIndex) throws SQLException { - try { - Object obj = getObject(columnIndex); - if (obj instanceof Number) { - return ((Number) obj).doubleValue(); - } else if (obj == null) { - return 0; - } else if (obj instanceof String) { + final Object obj = getObject(columnIndex); + if (obj == null) { + return 0.0; + } + if (obj instanceof Number) { + return ((Number) obj).doubleValue(); + } + if (obj instanceof String) { + try { return Double.parseDouble((String) obj); + } catch (Exception e) { + throw new KyuubiSQLException( + "Illegal conversion to double from column " + columnIndex + " [" + obj + "]", e); } - throw new Exception("Illegal conversion"); - } catch (Exception e) { - throw new KyuubiSQLException("Cannot convert column " + columnIndex + " to double: " + e, e); } + throw new KyuubiSQLException( + "Illegal conversion to double from column " + columnIndex + " [" + obj + "]"); } @Override @@ -238,19 +283,23 @@ public int getFetchDirection() throws SQLException { @Override public float getFloat(int columnIndex) throws SQLException { - try { - Object obj = getObject(columnIndex); - if (obj instanceof Number) { - return ((Number) obj).floatValue(); - } else if (obj == null) { - return 0; - } else if (obj instanceof String) { + final Object obj = getObject(columnIndex); + if (obj == null) { + return 0.0f; + } + if (obj instanceof Number) { + return ((Number) obj).floatValue(); + } + if (obj instanceof String) { + try { return Float.parseFloat((String) obj); + } catch (Exception e) { + throw new KyuubiSQLException( + "Illegal conversion to float from column " + columnIndex + " [" + obj + "]", e); } - throw new Exception("Illegal conversion"); - } catch (Exception e) { - throw new KyuubiSQLException("Cannot convert column " + columnIndex + " to float: " + e, e); } + throw new KyuubiSQLException( + "Illegal conversion to float from column " + columnIndex + " [" + obj + "]"); } @Override @@ -260,19 +309,23 @@ public float getFloat(String columnName) throws SQLException { @Override public int getInt(int columnIndex) throws SQLException { - try { - Object obj = getObject(columnIndex); - if (obj instanceof Number) { - return ((Number) obj).intValue(); - } else if (obj == null) { - return 0; - } else if (obj instanceof String) { + final Object obj = getObject(columnIndex); + if (obj == null) { + return 0; + } + if (obj instanceof Number) { + return ((Number) obj).intValue(); + } + if (obj instanceof String) { + try { return Integer.parseInt((String) obj); + } catch (Exception e) { + throw new KyuubiSQLException( + "Illegal conversion to int from column " + columnIndex + " [" + obj + "]", e); } - throw new Exception("Illegal conversion"); - } catch (Exception e) { - throw new KyuubiSQLException("Cannot convert column " + columnIndex + " to integer" + e, e); } + throw new KyuubiSQLException( + "Illegal conversion to int from column " + columnIndex + " [" + obj + "]"); } @Override @@ -282,19 +335,23 @@ public int getInt(String columnName) throws SQLException { @Override public long getLong(int columnIndex) throws SQLException { - try { - Object obj = getObject(columnIndex); - if (obj instanceof Number) { - return ((Number) obj).longValue(); - } else if (obj == null) { - return 0; - } else if (obj instanceof String) { + final Object obj = getObject(columnIndex); + if (obj == null) { + return 0L; + } + if (obj instanceof Number) { + return ((Number) obj).longValue(); + } + if (obj instanceof String) { + try { return Long.parseLong((String) obj); + } catch (Exception e) { + throw new KyuubiSQLException( + "Illegal conversion to long from column " + columnIndex + " [" + obj + "]", e); } - throw new Exception("Illegal conversion"); - } catch (Exception e) { - throw new KyuubiSQLException("Cannot convert column " + columnIndex + " to long: " + e, e); } + throw new KyuubiSQLException( + "Illegal conversion to long from column " + columnIndex + " [" + obj + "]"); } @Override @@ -309,23 +366,24 @@ public ResultSetMetaData getMetaData() throws SQLException { private Object getColumnValue(int columnIndex) throws SQLException { if (row == null) { - throw new KyuubiSQLException("No row found."); + throw new KyuubiSQLException("No row found"); } if (row.length == 0) { - throw new KyuubiSQLException("RowSet does not contain any columns!"); + throw new KyuubiSQLException("RowSet does not contain any columns"); } - if (columnIndex > row.length) { + // the first column is 1, the second is 2, ... + if (columnIndex <= 0 || columnIndex > row.length) { throw new KyuubiSQLException("Invalid columnIndex: " + columnIndex); } - TTypeId columnType = columnTypes.get(columnIndex - 1); + final TTypeId columnType = columnTypes.get(columnIndex - 1); + final Object value = row[columnIndex - 1]; try { - Object evaluated = evaluate(columnType, row[columnIndex - 1]); + final Object evaluated = evaluate(columnType, value); wasNull = evaluated == null; return evaluated; } catch (Exception e) { - e.printStackTrace(); - throw new KyuubiSQLException("Unrecognized column type:" + columnType, e); + throw new KyuubiSQLException("Failed to evaluate " + columnType + " [" + value + "]", e); } } @@ -336,7 +394,7 @@ private Object evaluate(TTypeId columnType, Object value) { switch (columnType) { case BINARY_TYPE: if (value instanceof String) { - return ((String) value).getBytes(); + return ((String) value).getBytes(StandardCharsets.UTF_8); } return value; case TIMESTAMP_TYPE: @@ -373,18 +431,21 @@ public Object getObject(String columnName) throws SQLException { @Override public short getShort(int columnIndex) throws SQLException { - try { - Object obj = getObject(columnIndex); - if (obj instanceof Number) { - return ((Number) obj).shortValue(); - } else if (obj == null) { - return 0; - } else if (obj instanceof String) { + final Object obj = getObject(columnIndex); + if (obj instanceof Number) { + return ((Number) obj).shortValue(); + } else if (obj == null) { + return 0; + } else if (obj instanceof String) { + try { return Short.parseShort((String) obj); + } catch (Exception e) { + throw new KyuubiSQLException( + "Illegal conversion to int from column " + columnIndex + " [" + obj + "]", e); } - throw new Exception("Illegal conversion"); - } catch (Exception e) { - throw new KyuubiSQLException("Cannot convert column " + columnIndex + " to short: " + e, e); + } else { + throw new KyuubiSQLException( + "Illegal conversion to int from column " + columnIndex + " [" + obj + "]"); } } @@ -404,12 +465,12 @@ public Statement getStatement() throws SQLException { */ @Override public String getString(int columnIndex) throws SQLException { - Object value = getColumnValue(columnIndex); - if (wasNull) { + final Object value = getColumnValue(columnIndex); + if (value == null) { return null; } if (value instanceof byte[]) { - return new String((byte[]) value); + return new String((byte[]) value, StandardCharsets.UTF_8); } return value.toString(); } @@ -421,7 +482,7 @@ public String getString(String columnName) throws SQLException { @Override public Timestamp getTimestamp(int columnIndex) throws SQLException { - Object obj = getObject(columnIndex); + final Object obj = getObject(columnIndex); if (obj == null) { return null; } @@ -429,9 +490,15 @@ public Timestamp getTimestamp(int columnIndex) throws SQLException { return (Timestamp) obj; } if (obj instanceof String) { - return Timestamp.valueOf((String) obj); + try { + return Timestamp.valueOf((String) obj); + } catch (Exception e) { + throw new KyuubiSQLException( + "Illegal conversion to int from column " + columnIndex + " [" + obj + "]", e); + } } - throw new KyuubiSQLException("Illegal conversion"); + throw new KyuubiSQLException( + "Illegal conversion to int from column " + columnIndex + " [" + obj + "]"); } @Override