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 2a4cd64cec..2c5653ae09 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 @@ -154,7 +154,7 @@ protected Accessor createAccessor(ColumnMetaData columnMetaData, case PRIMITIVE_INT: case INTEGER: case NUMBER: - return new TimeFromNumberAccessor(getter, localCalendar); + return new TimeFromNumberAccessor(getter, localCalendar, columnMetaData.precision); case JAVA_SQL_TIME: return new TimeAccessor(getter, localCalendar); default: @@ -165,11 +165,11 @@ protected Accessor createAccessor(ColumnMetaData columnMetaData, case PRIMITIVE_LONG: case LONG: case NUMBER: - return new TimestampFromNumberAccessor(getter, localCalendar); + return new TimestampFromNumberAccessor(getter, localCalendar, columnMetaData.precision); case JAVA_SQL_TIMESTAMP: - return new TimestampAccessor(getter, localCalendar); + return new TimestampAccessor(getter, localCalendar, columnMetaData.precision); case JAVA_UTIL_DATE: - return new TimestampFromUtilDateAccessor(getter, localCalendar); + return new TimestampFromUtilDateAccessor(getter, localCalendar, columnMetaData.precision); default: throw new AssertionError("bad " + columnMetaData.type.rep); } @@ -241,11 +241,11 @@ protected Accessor createAccessor(ColumnMetaData columnMetaData, /** Accesses a timestamp value as a string. * The timestamp is in SQL format (e.g. "2013-09-22 22:30:32"), * not Java format ("2013-09-22 22:30:32.123"). */ - private static String timestampAsString(long v, Calendar calendar) { + private static String timestampAsString(long v, Calendar calendar, int precision) { if (calendar != null) { v -= calendar.getTimeZone().getOffset(v); } - return DateTimeUtils.unixTimestampToString(v); + return DateTimeUtils.unixTimestampToString(v, precision); } /** Accesses a date value as a string, e.g. "2013-09-22". */ @@ -255,11 +255,11 @@ private static String dateAsString(int v, Calendar calendar) { } /** Accesses a time value as a string, e.g. "22:30:32". */ - private static String timeAsString(int v, Calendar calendar) { + private static String timeAsString(int v, Calendar calendar, int precision) { if (calendar != null) { v -= calendar.getTimeZone().getOffset(v); } - return DateTimeUtils.unixTimeToString(v); + return DateTimeUtils.unixTimeToString(v, precision); } /** Implementation of {@link Cursor.Accessor}. */ @@ -955,10 +955,12 @@ protected Number getNumber() throws SQLException { */ static class TimeFromNumberAccessor extends NumberAccessor { private final Calendar localCalendar; + private final int precision; - TimeFromNumberAccessor(Getter getter, Calendar localCalendar) { + TimeFromNumberAccessor(Getter getter, Calendar localCalendar, int precision) { super(getter, 0); this.localCalendar = localCalendar; + this.precision = precision; } @Override public Object getObject() throws SQLException { @@ -986,7 +988,7 @@ static class TimeFromNumberAccessor extends NumberAccessor { if (v == null) { return null; } - return timeAsString(v.intValue(), null); + return timeAsString(v.intValue(), null, this.precision); } protected Number getNumber() throws SQLException { @@ -1013,10 +1015,12 @@ protected Number getNumber() throws SQLException { */ static class TimestampFromNumberAccessor extends NumberAccessor { private final Calendar localCalendar; + private final int precision; - TimestampFromNumberAccessor(Getter getter, Calendar localCalendar) { + TimestampFromNumberAccessor(Getter getter, Calendar localCalendar, int precision) { super(getter, 0); this.localCalendar = localCalendar; + this.precision = precision; } @Override public Object getObject() throws SQLException { @@ -1052,7 +1056,7 @@ static class TimestampFromNumberAccessor extends NumberAccessor { if (v == null) { return null; } - return timestampAsString(v.longValue(), null); + return timestampAsString(v.longValue(), null, this.precision); } protected Number getNumber() throws SQLException { @@ -1155,7 +1159,8 @@ static class TimeAccessor extends ObjectAccessor { return null; } final int unix = DateTimeUtils.sqlTimeToUnixTime(time, localCalendar); - return timeAsString(unix, null); + // java.sql.Time only supports a precision of 0 + return timeAsString(unix, null, 0); } @Override public long getLong() throws SQLException { @@ -1178,10 +1183,12 @@ static class TimeAccessor extends ObjectAccessor { */ static class TimestampAccessor extends ObjectAccessor { private final Calendar localCalendar; + private final int precision; - TimestampAccessor(Getter getter, Calendar localCalendar) { + TimestampAccessor(Getter getter, Calendar localCalendar, int precision) { super(getter); this.localCalendar = localCalendar; + this.precision = precision; } @Override public Timestamp getTimestamp(Calendar calendar) throws SQLException { @@ -1220,7 +1227,7 @@ static class TimestampAccessor extends ObjectAccessor { } final long unix = DateTimeUtils.sqlTimestampToUnixTimestamp(timestamp, localCalendar); - return timestampAsString(unix, null); + return timestampAsString(unix, null, this.precision); } @Override public long getLong() throws SQLException { @@ -1241,11 +1248,13 @@ static class TimestampAccessor extends ObjectAccessor { */ static class TimestampFromUtilDateAccessor extends ObjectAccessor { private final Calendar localCalendar; + private final int precision; TimestampFromUtilDateAccessor(Getter getter, - Calendar localCalendar) { + Calendar localCalendar, int precision) { super(getter); this.localCalendar = localCalendar; + this.precision = precision; } @Override public Timestamp getTimestamp(Calendar calendar) throws SQLException { @@ -1282,7 +1291,7 @@ static class TimestampFromUtilDateAccessor extends ObjectAccessor { return null; } final long unix = DateTimeUtils.utilDateToUnixTimestamp(date, localCalendar); - return timestampAsString(unix, null); + return timestampAsString(unix, null, this.precision); } @Override public long getLong() throws SQLException { diff --git a/core/src/test/java/org/apache/calcite/avatica/util/TimeFromNumberAccessorTest.java b/core/src/test/java/org/apache/calcite/avatica/util/TimeFromNumberAccessorTest.java index 87f557dc8f..efa7a87094 100644 --- a/core/src/test/java/org/apache/calcite/avatica/util/TimeFromNumberAccessorTest.java +++ b/core/src/test/java/org/apache/calcite/avatica/util/TimeFromNumberAccessorTest.java @@ -48,7 +48,7 @@ public class TimeFromNumberAccessorTest { final AbstractCursor.Getter getter = new LocalGetter(); localCalendar = Calendar.getInstance(TimeZone.getDefault(), Locale.ROOT); instance = new AbstractCursor.TimeFromNumberAccessor(getter, - localCalendar); + localCalendar, 0); } /** @@ -156,4 +156,26 @@ private class LocalGetter implements AbstractCursor.Getter { return value == null; } } + + /** + * Test case for [CALCITE-6282] + * Avatica ignores time precision when returning TIME results. */ + @Test public void testPrecision() throws SQLException { + final AbstractCursor.Getter getter = new AbstractCursor.Getter() { + @Override + public Object getObject() throws SQLException { + // This is the representation of TIME '12:42:25.34' + return 45745340; + } + + @Override + public boolean wasNull() throws SQLException { + return false; + } + }; + AbstractCursor.TimeFromNumberAccessor accessor = new AbstractCursor.TimeFromNumberAccessor( + getter, null, 2); + String string = accessor.getString(); + assertThat(string, is("12:42:25.34")); + } } diff --git a/core/src/test/java/org/apache/calcite/avatica/util/TimestampAccessorTest.java b/core/src/test/java/org/apache/calcite/avatica/util/TimestampAccessorTest.java index 98d20c00f4..573c59e6a8 100644 --- a/core/src/test/java/org/apache/calcite/avatica/util/TimestampAccessorTest.java +++ b/core/src/test/java/org/apache/calcite/avatica/util/TimestampAccessorTest.java @@ -60,7 +60,7 @@ public class TimestampAccessorTest { @Before public void before() { final AbstractCursor.Getter getter = new LocalGetter(); localCalendar = Calendar.getInstance(TimeZone.getDefault(), Locale.ROOT); - instance = new AbstractCursor.TimestampAccessor(getter, localCalendar); + instance = new AbstractCursor.TimestampAccessor(getter, localCalendar, 0); } /** diff --git a/core/src/test/java/org/apache/calcite/avatica/util/TimestampFromNumberAccessorTest.java b/core/src/test/java/org/apache/calcite/avatica/util/TimestampFromNumberAccessorTest.java index d8fd819fbf..8ca94ff8f2 100644 --- a/core/src/test/java/org/apache/calcite/avatica/util/TimestampFromNumberAccessorTest.java +++ b/core/src/test/java/org/apache/calcite/avatica/util/TimestampFromNumberAccessorTest.java @@ -40,6 +40,10 @@ public class TimestampFromNumberAccessorTest { // UTC: 2014-09-30 15:28:27.356 private static final long DST_INSTANT = 1412090907356L; private static final String DST_STRING = "2014-09-30 15:28:27"; + // With precision 2 + private static final String DST_STRING_2 = "2014-09-30 15:28:27.35"; + // With precision 3 + private static final String DST_STRING_3 = "2014-09-30 15:28:27.356"; // UTC: 1500-04-30 12:00:00.123 (JULIAN CALENDAR) private static final long PRE_GREG_INSTANT = -14821444799877L; @@ -56,7 +60,7 @@ public class TimestampFromNumberAccessorTest { @Before public void before() { final AbstractCursor.Getter getter = new LocalGetter(); localCalendar = Calendar.getInstance(TimeZone.getDefault(), Locale.ROOT); - instance = new AbstractCursor.TimestampFromNumberAccessor(getter, localCalendar); + instance = new AbstractCursor.TimestampFromNumberAccessor(getter, localCalendar, 0); } /** @@ -72,6 +76,31 @@ public class TimestampFromNumberAccessorTest { is(Timestamp.valueOf("1500-04-30 12:00:00"))); } + /** + * Test case for + * [CALCITE-6282] Avatica ignores time precision when returning TIME results. */ + @Test public void testPrecision() throws SQLException { + final AbstractCursor.Getter getter = new AbstractCursor.Getter() { + @Override + public Object getObject() throws SQLException { + return DST_INSTANT; + } + + @Override + public boolean wasNull() throws SQLException { + return false; + } + }; + AbstractCursor.TimestampFromNumberAccessor accessor = + new AbstractCursor.TimestampFromNumberAccessor(getter, null, 2); + String string = accessor.getString(); + assertThat(string, is(DST_STRING_2)); + accessor = new AbstractCursor.TimestampFromNumberAccessor( + getter, null, 3); + string = accessor.getString(); + assertThat(string, is(DST_STRING_3)); + } + /** * Test {@code getDate()} handles time zone conversions relative to the local calendar and not * UTC. diff --git a/core/src/test/java/org/apache/calcite/avatica/util/TimestampFromUtilDateAccessorTest.java b/core/src/test/java/org/apache/calcite/avatica/util/TimestampFromUtilDateAccessorTest.java index bdc7db1fa2..ec214bb701 100644 --- a/core/src/test/java/org/apache/calcite/avatica/util/TimestampFromUtilDateAccessorTest.java +++ b/core/src/test/java/org/apache/calcite/avatica/util/TimestampFromUtilDateAccessorTest.java @@ -61,7 +61,7 @@ public class TimestampFromUtilDateAccessorTest { @Before public void before() { final AbstractCursor.Getter getter = new LocalGetter(); localCalendar = Calendar.getInstance(TimeZone.getDefault(), Locale.ROOT); - instance = new AbstractCursor.TimestampFromUtilDateAccessor(getter, localCalendar); + instance = new AbstractCursor.TimestampFromUtilDateAccessor(getter, localCalendar, 0); } /**