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

[CALCITE-4600] ClassCastException for arrays with date/time/timestamp elements #154

Closed
wants to merge 6 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -919,6 +945,19 @@ private DateFromNumberAccessor(Getter getter, Calendar localCalendar) {
}
return dateAsString(v.intValue(), null);
}

protected Number getNumber() throws SQLException {
final Object value = super.getObject();
if (value == null) {
return null;
}
if (value instanceof Date) {
long time = ((Date) value).getTime();
time -= localCalendar.getTimeZone().getOffset(time);
return time / DateTimeUtils.MILLIS_PER_DAY;
}
return (Number) value;
}
}

/**
Expand Down Expand Up @@ -961,6 +1000,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;
}
}

/**
Expand Down Expand Up @@ -989,15 +1039,15 @@ 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;
}
return new Date(timestamp.getTime());
}

@Override public Time getTime(Calendar calendar) throws SQLException {
final Timestamp timestamp = getTimestamp(calendar);
final Timestamp timestamp = getTimestamp(calendar);
if (timestamp == null) {
return null;
}
Expand All @@ -1013,6 +1063,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;
}
}

/**
Expand Down
14 changes: 12 additions & 2 deletions core/src/main/java/org/apache/calcite/avatica/util/ArrayImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@
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;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
Expand Down Expand Up @@ -180,7 +182,35 @@ 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),
columnMetaData("decimal_array", 18,
ColumnMetaData.array(
ColumnMetaData.scalar(Types.DECIMAL, "DECIMAL",
ColumnMetaData.Rep.PRIMITIVE_DOUBLE),
"ARRAY",
ColumnMetaData.Rep.ARRAY),
DatabaseMetaData.columnNoNulls));

List<Object> row = Collections.<Object>singletonList(
new Object[] {
Expand All @@ -189,8 +219,12 @@ 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),
Arrays.asList(1476123L, 147912242L),
Arrays.asList(1, 1.1)
});

CursorFactory factory = CursorFactory.deduce(columns, null);
Expand Down Expand Up @@ -570,6 +604,78 @@ 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.
*/
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.
*/
Expand All @@ -584,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.
*/
Expand Down Expand Up @@ -1043,7 +1166,19 @@ public static Collection<AccessorTestHelper> 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 BooleanAccessorTestHelper(new OrdinalGetter(14)),
new BooleanAccessorTestHelper(new LabelGetter("bit")),
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)),
new TimestampArrayAccessorTestHelper(new LabelGetter("timestamp_array")),
new TimeArrayAccessorTestHelper(new OrdinalGetter(18)),
new TimeArrayAccessorTestHelper(new LabelGetter("time_array")),
new DecimalArrayAccessorTestHelper(new OrdinalGetter(19)),
new DecimalArrayAccessorTestHelper(new LabelGetter("decimal_array")));
}

private final AccessorTestHelper testHelper;
Expand Down