From 4d40288ac3825d04a842518690d9394b0d0b40b0 Mon Sep 17 00:00:00 2001 From: snuyanzin Date: Mon, 10 May 2021 03:04:20 +0200 Subject: [PATCH 1/6] [CALCITE-4600] Support Date/Time/Timestamp slot object's content for date/time/timestamp array accessors --- .../calcite/avatica/util/AbstractCursor.java | 45 ++++++++-- .../calcite/avatica/util/ArrayImpl.java | 14 ++- .../AvaticaResultSetConversionsTest.java | 89 ++++++++++++++++++- 3 files changed, 138 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java index fa357d55eb..38522cec5f 100644 --- a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java +++ b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java @@ -896,15 +896,15 @@ private DateFromNumberAccessor(Getter getter, Calendar localCalendar) { } @Override public Date getDate(Calendar calendar) throws SQLException { - final Number v = getNumber(); + final Number v = getDate(); if (v == null) { return null; } - return longToDate(v.longValue() * DateTimeUtils.MILLIS_PER_DAY, calendar); + return longToDate(((Number) v).longValue() * DateTimeUtils.MILLIS_PER_DAY, calendar); } @Override public Timestamp getTimestamp(Calendar calendar) throws SQLException { - final Number v = getNumber(); + final Number v = getDate(); if (v == null) { return null; } @@ -913,12 +913,23 @@ private DateFromNumberAccessor(Getter getter, Calendar localCalendar) { } @Override public String getString() throws SQLException { - final Number v = getNumber(); + final Number v = getDate(); if (v == null) { return null; } return dateAsString(v.intValue(), null); } + + protected Number getDate() throws SQLException { + final Object value = super.getObject(); + if (value == null) { + return null; + } + if (value instanceof Date) { + return ((Date) value).toLocalDate().toEpochDay(); + } + return (Number) value; + } } /** @@ -961,6 +972,17 @@ private TimeFromNumberAccessor(Getter getter, Calendar localCalendar) { } return timeAsString(v.intValue(), null); } + + protected Number getNumber() throws SQLException { + final Object v = super.getObject(); + if (v == null) { + return null; + } + if (v instanceof Time) { + return ((Time) v).getTime(); + } + return (Number) v; + } } /** @@ -989,7 +1011,7 @@ private TimestampFromNumberAccessor(Getter getter, Calendar localCalendar) { } @Override public Date getDate(Calendar calendar) throws SQLException { - final Timestamp timestamp = getTimestamp(calendar); + final Timestamp timestamp = getTimestamp(calendar); if (timestamp == null) { return null; } @@ -997,7 +1019,7 @@ private TimestampFromNumberAccessor(Getter getter, Calendar localCalendar) { } @Override public Time getTime(Calendar calendar) throws SQLException { - final Timestamp timestamp = getTimestamp(calendar); + final Timestamp timestamp = getTimestamp(calendar); if (timestamp == null) { return null; } @@ -1013,6 +1035,17 @@ private TimestampFromNumberAccessor(Getter getter, Calendar localCalendar) { } return timestampAsString(v.longValue(), null); } + + protected Number getNumber() throws SQLException { + final Object v = super.getObject(); + if (v == null) { + return null; + } + if (v instanceof Timestamp) { + return ((Timestamp) v).getTime(); + } + return (Number) v; + } } /** diff --git a/core/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java b/core/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java index 3cdc70a2f3..e868d36dc9 100644 --- a/core/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java +++ b/core/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java @@ -227,11 +227,21 @@ Object getArrayData(Object o, AbstractCursor.ArrayAccessor componentAccessor) * Elements are compared using {@link Object#equals(Object)}, and null * values are equal to each other. */ public static boolean equalContents(Array left, Array right) throws SQLException { + if (left.getBaseType() != right.getBaseType()) { + return false; + } ResultSet leftResultSet = left.getResultSet(); ResultSet rightResultSet = right.getResultSet(); + int leftColumnCount = leftResultSet.getMetaData().getColumnCount(); + int rightColumnCount = rightResultSet.getMetaData().getColumnCount(); + if (leftColumnCount != rightColumnCount) { + return false; + } while (leftResultSet.next() && rightResultSet.next()) { - if (!Objects.equals(leftResultSet.getObject(1), rightResultSet.getObject(1))) { - return false; + for (int i = 1; i <= leftColumnCount; i++) { + if (!Objects.equals(leftResultSet.getObject(i), rightResultSet.getObject(i))) { + return false; + } } } return !leftResultSet.next() && !rightResultSet.next(); diff --git a/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java b/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java index c49e047051..53c338480e 100644 --- a/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java +++ b/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java @@ -57,6 +57,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.TimeZone; import static org.hamcrest.CoreMatchers.isA; import static org.junit.Assert.assertEquals; @@ -180,7 +181,28 @@ public TestMetaImpl(AvaticaConnection connection) { columnMetaData("null", 14, ColumnMetaData.scalar(Types.NULL, "NULL", ColumnMetaData.Rep.OBJECT), - DatabaseMetaData.columnNullable)); + DatabaseMetaData.columnNullable), + columnMetaData("date_array", 15, + ColumnMetaData.array( + ColumnMetaData.scalar(Types.DATE, "DATE", + ColumnMetaData.Rep.PRIMITIVE_INT), + "ARRAY", + ColumnMetaData.Rep.ARRAY), + DatabaseMetaData.columnNoNulls), + columnMetaData("timestamp_array", 16, + ColumnMetaData.array( + ColumnMetaData.scalar(Types.TIMESTAMP, "TIMESTAMP", + ColumnMetaData.Rep.PRIMITIVE_LONG), + "ARRAY", + ColumnMetaData.Rep.ARRAY), + DatabaseMetaData.columnNoNulls), + columnMetaData("time_array", 17, + ColumnMetaData.array( + ColumnMetaData.scalar(Types.TIME, "TIME", + ColumnMetaData.Rep.NUMBER), + "ARRAY", + ColumnMetaData.Rep.ARRAY), + DatabaseMetaData.columnNoNulls)); List row = Collections.singletonList( new Object[] { @@ -191,6 +213,9 @@ public TestMetaImpl(AvaticaConnection connection) { new StructImpl(Arrays.asList(42, false)), false, null, + Arrays.asList(123, 18234), + Arrays.asList(1476130718123L, 1479123123242L), + Arrays.asList(1476123L, 147912242L) }); CursorFactory factory = CursorFactory.deduce(columns, null); @@ -570,6 +595,60 @@ private ArrayAccessorTestHelper(Getter g) { } } + /** + * Accessor test helper for date array column. + */ + private static final class DateArrayAccessorTestHelper extends AccessorTestHelper { + private DateArrayAccessorTestHelper(Getter g) { + super(g); + } + + @Override public void testGetArray(ResultSet resultSet) throws SQLException { + ColumnMetaData.ScalarType intType = + ColumnMetaData.scalar(Types.DATE, "DATE", ColumnMetaData.Rep.PRIMITIVE_INT); + Array expectedArray = + new ArrayFactoryImpl(TimeZone.getTimeZone("UTC")).createArray( + intType, Arrays.asList(123, 18234)); + assertTrue(ArrayImpl.equalContents(expectedArray, g.getArray(resultSet))); + } + } + + /** + * Accessor test helper for time array column. + */ + private static final class TimeArrayAccessorTestHelper extends AccessorTestHelper { + private TimeArrayAccessorTestHelper(Getter g) { + super(g); + } + + @Override public void testGetArray(ResultSet resultSet) throws SQLException { + ColumnMetaData.ScalarType intType = + ColumnMetaData.scalar(Types.TIME, "TIME", ColumnMetaData.Rep.NUMBER); + Array expectedArray = + new ArrayFactoryImpl(TimeZone.getTimeZone("UTC")).createArray( + intType, Arrays.asList(1476123L, 147912242L)); + assertTrue(ArrayImpl.equalContents(expectedArray, g.getArray(resultSet))); + } + } + + /** + * Accessor test helper for timestamp array column. + */ + private static final class TimestampArrayAccessorTestHelper extends AccessorTestHelper { + private TimestampArrayAccessorTestHelper(Getter g) { + super(g); + } + + @Override public void testGetArray(ResultSet resultSet) throws SQLException { + ColumnMetaData.ScalarType intType = + ColumnMetaData.scalar(Types.TIMESTAMP, "TIMESTAMP", ColumnMetaData.Rep.PRIMITIVE_LONG); + Array expectedArray = + new ArrayFactoryImpl(TimeZone.getTimeZone("UTC")).createArray( + intType, Arrays.asList(1476130718123L, 1479123123242L)); + assertTrue(ArrayImpl.equalContents(expectedArray, g.getArray(resultSet))); + } + } + /** * Accessor test helper for row column. */ @@ -1043,7 +1122,13 @@ public static Collection data() { new ArrayAccessorTestHelper(new OrdinalGetter(12)), new ArrayAccessorTestHelper(new LabelGetter("array")), new StructAccessorTestHelper(new OrdinalGetter(13)), - new StructAccessorTestHelper(new LabelGetter("struct"))); + new StructAccessorTestHelper(new LabelGetter("struct")), + new DateArrayAccessorTestHelper(new OrdinalGetter(16)), + new DateArrayAccessorTestHelper(new LabelGetter("date_array")), + new TimestampArrayAccessorTestHelper(new OrdinalGetter(17)), + new TimestampArrayAccessorTestHelper(new LabelGetter("timestamp_array")), + new TimeArrayAccessorTestHelper(new OrdinalGetter(18)), + new TimeArrayAccessorTestHelper(new LabelGetter("time_array"))); } private final AccessorTestHelper testHelper; From 6b051fb7a084b85fdb31641bd00d5ddd8ed22ac3 Mon Sep 17 00:00:00 2001 From: snuyanzin Date: Mon, 10 May 2021 21:41:41 +0200 Subject: [PATCH 2/6] [CALCITE-4600] Method naming DateFromNumberAccessor#getDate => DateFromNumberAccessor#getNumber --- .../apache/calcite/avatica/util/AbstractCursor.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java index 38522cec5f..9ff13697b6 100644 --- a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java +++ b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java @@ -896,15 +896,15 @@ private DateFromNumberAccessor(Getter getter, Calendar localCalendar) { } @Override public Date getDate(Calendar calendar) throws SQLException { - final Number v = getDate(); + final Number v = getNumber(); if (v == null) { return null; } - return longToDate(((Number) v).longValue() * DateTimeUtils.MILLIS_PER_DAY, calendar); + return longToDate(v.longValue() * DateTimeUtils.MILLIS_PER_DAY, calendar); } @Override public Timestamp getTimestamp(Calendar calendar) throws SQLException { - final Number v = getDate(); + final Number v = getNumber(); if (v == null) { return null; } @@ -913,14 +913,14 @@ private DateFromNumberAccessor(Getter getter, Calendar localCalendar) { } @Override public String getString() throws SQLException { - final Number v = getDate(); + final Number v = getNumber(); if (v == null) { return null; } return dateAsString(v.intValue(), null); } - protected Number getDate() throws SQLException { + protected Number getNumber() throws SQLException { final Object value = super.getObject(); if (value == null) { return null; From 8aa0afac346418ed00435771f706bfb3162c5300 Mon Sep 17 00:00:00 2001 From: snuyanzin Date: Tue, 11 May 2021 15:54:30 +0200 Subject: [PATCH 3/6] [CALCITE-4602] Array could contain int and float elements --- .../calcite/avatica/util/AbstractCursor.java | 26 +++++++++++++++ .../AvaticaResultSetConversionsTest.java | 32 +++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java index 9ff13697b6..20a1e05e2f 100644 --- a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java +++ b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java @@ -682,6 +682,32 @@ public boolean getBoolean() throws SQLException { } } + /** + * Accessor that assumes that the underlying value is a {@link BigDecimal}; + * corresponds to {@link java.sql.Types#DECIMAL}. + */ + private static class BigDecimalAccessor extends BigNumberAccessor { + private BigDecimalAccessor(Getter getter) { + super(getter); + } + + protected Number getNumber() throws SQLException { + return (Number) getObject(); + } + + public BigDecimal getBigDecimal(int scale) throws SQLException { + Number number = getNumber(); + return number == null || number instanceof BigDecimal + ? (BigDecimal) number : BigDecimal.valueOf(number.longValue()); + } + + public BigDecimal getBigDecimal() throws SQLException { + Number number = getNumber(); + return number == null || number instanceof BigDecimal + ? (BigDecimal) number : BigDecimal.valueOf(number.longValue()); + } + } + /** * Accessor that assumes that the underlying value is a {@link Number}; * corresponds to {@link java.sql.Types#NUMERIC} diff --git a/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java b/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java index 53c338480e..ac86396dca 100644 --- a/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java +++ b/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java @@ -202,6 +202,13 @@ public TestMetaImpl(AvaticaConnection connection) { ColumnMetaData.Rep.NUMBER), "ARRAY", ColumnMetaData.Rep.ARRAY), + DatabaseMetaData.columnNoNulls), + columnMetaData("decimal_array", 18, + ColumnMetaData.array( + ColumnMetaData.scalar(Types.DECIMAL, "DECIMAL", + ColumnMetaData.Rep.PRIMITIVE_DOUBLE), + "ARRAY", + ColumnMetaData.Rep.ARRAY), DatabaseMetaData.columnNoNulls)); List row = Collections.singletonList( @@ -215,7 +222,8 @@ public TestMetaImpl(AvaticaConnection connection) { null, Arrays.asList(123, 18234), Arrays.asList(1476130718123L, 1479123123242L), - Arrays.asList(1476123L, 147912242L) + Arrays.asList(1476123L, 147912242L), + Arrays.asList(1, 1.1) }); CursorFactory factory = CursorFactory.deduce(columns, null); @@ -595,6 +603,24 @@ private ArrayAccessorTestHelper(Getter g) { } } + /** + * Accessor test helper for decimal array column. + */ + private static final class DecimalArrayAccessorTestHelper extends AccessorTestHelper { + private DecimalArrayAccessorTestHelper(Getter g) { + super(g); + } + + @Override public void testGetArray(ResultSet resultSet) throws SQLException { + ColumnMetaData.ScalarType intType = + ColumnMetaData.scalar(Types.DECIMAL, "DECIMAL", ColumnMetaData.Rep.PRIMITIVE_DOUBLE); + Array expectedArray = + new ArrayFactoryImpl(Unsafe.localCalendar().getTimeZone()).createArray( + intType, Arrays.asList(1, 1.1)); + assertTrue(ArrayImpl.equalContents(expectedArray, g.getArray(resultSet))); + } + } + /** * Accessor test helper for date array column. */ @@ -1128,7 +1154,9 @@ public static Collection data() { new TimestampArrayAccessorTestHelper(new OrdinalGetter(17)), new TimestampArrayAccessorTestHelper(new LabelGetter("timestamp_array")), new TimeArrayAccessorTestHelper(new OrdinalGetter(18)), - new TimeArrayAccessorTestHelper(new LabelGetter("time_array"))); + new TimeArrayAccessorTestHelper(new LabelGetter("time_array")), + new DecimalArrayAccessorTestHelper(new OrdinalGetter(19)), + new DecimalArrayAccessorTestHelper(new LabelGetter("decimal_array"))); } private final AccessorTestHelper testHelper; From d78e2fd06e84298362e9d051529f3d7323a21279 Mon Sep 17 00:00:00 2001 From: Alessandro Solimando Date: Mon, 27 Sep 2021 21:06:13 +0200 Subject: [PATCH 4/6] [CALCITE-4757] Allow columns of type Null in ResultSet (fixed tests) --- .../AvaticaResultSetConversionsTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java b/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java index ac86396dca..8ac9337713 100644 --- a/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java +++ b/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java @@ -61,6 +61,7 @@ import static org.hamcrest.CoreMatchers.isA; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -689,6 +690,23 @@ private StructAccessorTestHelper(Getter g) { } } + /** + * Accessor test helper for the (null) object column. + */ + private static final class NullObjectAccessorTestHelper extends AccessorTestHelper { + private NullObjectAccessorTestHelper(Getter g) { + super(g); + } + + @Override public void testGetString(ResultSet resultSet) throws SQLException { + assertNull(g.getString(resultSet)); + } + + @Override public void testGetObject(ResultSet resultSet) throws SQLException { + assertNull(g.getObject(resultSet)); + } + } + /** * Accessor test helper for the byte column. */ @@ -1149,6 +1167,8 @@ public static Collection data() { new ArrayAccessorTestHelper(new LabelGetter("array")), new StructAccessorTestHelper(new OrdinalGetter(13)), new StructAccessorTestHelper(new LabelGetter("struct")), + new NullObjectAccessorTestHelper(new OrdinalGetter(15)), + new NullObjectAccessorTestHelper(new LabelGetter("null")), new DateArrayAccessorTestHelper(new OrdinalGetter(16)), new DateArrayAccessorTestHelper(new LabelGetter("date_array")), new TimestampArrayAccessorTestHelper(new OrdinalGetter(17)), From 4ec14933fc7b5020f09314ec8b537e662d27c41c Mon Sep 17 00:00:00 2001 From: Alessandro Solimando Date: Mon, 27 Sep 2021 21:09:02 +0200 Subject: [PATCH 5/6] [CALCITE-4536] Add support for BIT data type (fixed tests) --- .../calcite/avatica/AvaticaResultSetConversionsTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java b/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java index 8ac9337713..b8df74e0ad 100644 --- a/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java +++ b/core/src/test/java/org/apache/calcite/avatica/AvaticaResultSetConversionsTest.java @@ -219,7 +219,7 @@ public TestMetaImpl(AvaticaConnection connection) { new Timestamp(1476130718123L), Arrays.asList(1, 2, 3), new StructImpl(Arrays.asList(42, false)), - false, + true, null, Arrays.asList(123, 18234), Arrays.asList(1476130718123L, 1479123123242L), @@ -1167,6 +1167,8 @@ public static Collection data() { new ArrayAccessorTestHelper(new LabelGetter("array")), new StructAccessorTestHelper(new OrdinalGetter(13)), new StructAccessorTestHelper(new LabelGetter("struct")), + new BooleanAccessorTestHelper(new OrdinalGetter(14)), + new BooleanAccessorTestHelper(new LabelGetter("bit")), new NullObjectAccessorTestHelper(new OrdinalGetter(15)), new NullObjectAccessorTestHelper(new LabelGetter("null")), new DateArrayAccessorTestHelper(new OrdinalGetter(16)), From 1ad73512d06e4b4fe78fce4ec2d5c912a837ffe2 Mon Sep 17 00:00:00 2001 From: snuyanzin Date: Sun, 3 Oct 2021 10:39:14 +0200 Subject: [PATCH 6/6] [CALCITE-4600] Take into account timezone offset --- .../java/org/apache/calcite/avatica/util/AbstractCursor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java index 20a1e05e2f..f05cbcc6d3 100644 --- a/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java +++ b/core/src/main/java/org/apache/calcite/avatica/util/AbstractCursor.java @@ -952,7 +952,9 @@ protected Number getNumber() throws SQLException { return null; } if (value instanceof Date) { - return ((Date) value).toLocalDate().toEpochDay(); + long time = ((Date) value).getTime(); + time -= localCalendar.getTimeZone().getOffset(time); + return time / DateTimeUtils.MILLIS_PER_DAY; } return (Number) value; }