[CALCITE-1947] Add time/timestamp with local time zone types to optimizer
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/68abba04 Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/68abba04 Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/68abba04 Branch: refs/heads/master Commit: 68abba04a685712225c83e3fdcde8ecf429dea9f Parents: f10950b Author: Jesus Camacho Rodriguez <[email protected]> Authored: Mon Aug 14 17:46:42 2017 -0700 Committer: Jesus Camacho Rodriguez <[email protected]> Committed: Tue Sep 5 14:54:35 2017 -0700 ---------------------------------------------------------------------- .../calcite/adapter/enumerable/RexImpTable.java | 15 +- .../adapter/enumerable/RexToLixTranslator.java | 153 +++++++++++ .../calcite/jdbc/JavaTypeFactoryImpl.java | 2 + .../apache/calcite/rel/metadata/RelMdSize.java | 4 + .../rel/rules/SortProjectTransposeRule.java | 3 +- .../calcite/rel/type/RelDataTypeSystemImpl.java | 8 + .../java/org/apache/calcite/rex/RexBuilder.java | 57 +++++ .../java/org/apache/calcite/rex/RexLiteral.java | 26 ++ .../org/apache/calcite/rex/RexSimplify.java | 1 + .../apache/calcite/runtime/SqlFunctions.java | 98 ++++++++ .../java/org/apache/calcite/schema/Schemas.java | 4 +- .../apache/calcite/sql/SqlJdbcDataTypeName.java | 2 + .../sql/type/SqlTypeAssignmentRules.java | 34 +++ .../apache/calcite/sql/type/SqlTypeFamily.java | 6 +- .../apache/calcite/sql/type/SqlTypeName.java | 11 +- .../sql2rel/StandardConvertletTable.java | 26 ++ .../org/apache/calcite/util/BuiltInMethod.java | 28 ++- .../org/apache/calcite/util/DateString.java | 2 +- .../calcite/util/DateTimeStringUtils.java | 88 +++++++ .../org/apache/calcite/util/TimeString.java | 6 +- .../calcite/util/TimeWithTimeZoneString.java | 188 ++++++++++++++ .../apache/calcite/util/TimestampString.java | 52 +--- .../util/TimestampWithTimeZoneString.java | 194 ++++++++++++++ .../calcite/jdbc/CalciteRemoteDriverTest.java | 2 +- .../org/apache/calcite/rex/RexBuilderTest.java | 73 ++++++ .../org/apache/calcite/test/CalciteAssert.java | 3 + .../org/apache/calcite/test/RexProgramTest.java | 128 +++++++++- .../adapter/druid/DruidConnectionImpl.java | 2 +- .../adapter/druid/DruidDateTimeUtils.java | 74 +++--- .../calcite/adapter/druid/DruidQuery.java | 19 +- .../calcite/adapter/druid/DruidRules.java | 6 +- .../adapter/druid/DruidTableFactory.java | 2 +- .../druid/TimeExtractionDimensionSpec.java | 14 +- .../adapter/druid/TimeExtractionFunction.java | 21 +- .../adapter/druid/DruidQueryFilterTest.java | 2 +- .../org/apache/calcite/test/DruidAdapterIT.java | 252 +++++++++---------- .../calcite/test/DruidDateRangeRulesTest.java | 4 +- site/_docs/reference.md | 9 +- 38 files changed, 1361 insertions(+), 258 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java index 0c1f542..0b3aa84 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java @@ -1655,7 +1655,14 @@ public class RexImpTable { case 2: final Type type; final Method floorMethod; + Expression operand = translatedOperands.get(0); switch (call.getType().getSqlTypeName()) { + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + operand = Expressions.call( + BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method, + operand, + Expressions.call(BuiltInMethod.TIME_ZONE.method, translator.getRoot())); + // fall through case TIMESTAMP: type = long.class; floorMethod = timestampMethod; @@ -1671,19 +1678,19 @@ public class RexImpTable { case YEAR: case MONTH: return Expressions.call(floorMethod, tur, - call(translatedOperands, type, TimeUnit.DAY)); + call(operand, type, TimeUnit.DAY)); default: - return call(translatedOperands, type, timeUnitRange.startUnit); + return call(operand, type, timeUnitRange.startUnit); } default: throw new AssertionError(); } } - private Expression call(List<Expression> translatedOperands, Type type, + private Expression call(Expression operand, Type type, TimeUnit timeUnit) { return Expressions.call(SqlFunctions.class, methodName, - Types.castIfNecessary(type, translatedOperands.get(0)), + Types.castIfNecessary(type, operand), Types.castIfNecessary(type, Expressions.constant(timeUnit.multiplier))); } http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java index 10e969f..270db82 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java @@ -245,6 +245,14 @@ public class RexToLixTranslator { Expressions.call(BuiltInMethod.FLOOR_DIV.method, operand, Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)), int.class); + break; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + convert = RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_DATE.method, + operand, + Expressions.call(BuiltInMethod.TIME_ZONE.method, root))); } break; case TIME: @@ -254,6 +262,14 @@ public class RexToLixTranslator { convert = Expressions.call(BuiltInMethod.STRING_TO_TIME.method, operand); break; + case TIME_WITH_LOCAL_TIME_ZONE: + convert = RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_TIME.method, + operand, + Expressions.call(BuiltInMethod.TIME_ZONE.method, root))); + break; case TIMESTAMP: convert = Expressions.convert_( Expressions.call( @@ -261,6 +277,49 @@ public class RexToLixTranslator { operand, Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)), int.class); + break; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + convert = RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME.method, + operand, + Expressions.call(BuiltInMethod.TIME_ZONE.method, root))); + } + break; + case TIME_WITH_LOCAL_TIME_ZONE: + switch (sourceType.getSqlTypeName()) { + case CHAR: + case VARCHAR: + convert = + Expressions.call(BuiltInMethod.STRING_TO_TIME_WITH_LOCAL_TIME_ZONE.method, operand); + break; + case TIME: + convert = Expressions.call( + BuiltInMethod.TIME_STRING_TO_TIME_WITH_LOCAL_TIME_ZONE.method, + RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.UNIX_TIME_TO_STRING.method, + operand)), + Expressions.call(BuiltInMethod.TIME_ZONE.method, root)); + break; + case TIMESTAMP: + convert = Expressions.call( + BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method, + RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method, + operand)), + Expressions.call(BuiltInMethod.TIME_ZONE.method, root)); + break; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + convert = RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME_WITH_LOCAL_TIME_ZONE.method, + operand)); } break; case TIMESTAMP: @@ -285,6 +344,82 @@ public class RexToLixTranslator { Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)), Expressions.convert_(operand, long.class)); break; + case TIME_WITH_LOCAL_TIME_ZONE: + convert = RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method, + Expressions.call( + BuiltInMethod.UNIX_DATE_TO_STRING.method, + Expressions.call(BuiltInMethod.CURRENT_DATE.method, root)), + operand, + Expressions.call(BuiltInMethod.TIME_ZONE.method, root))); + break; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + convert = RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method, + operand, + Expressions.call(BuiltInMethod.TIME_ZONE.method, root))); + } + break; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + switch (sourceType.getSqlTypeName()) { + case CHAR: + case VARCHAR: + convert = + Expressions.call( + BuiltInMethod.STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method, + operand); + break; + case DATE: + convert = Expressions.call( + BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method, + RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method, + Expressions.multiply( + Expressions.convert_(operand, long.class), + Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)))), + Expressions.call(BuiltInMethod.TIME_ZONE.method, root)); + break; + case TIME: + convert = Expressions.call( + BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method, + RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method, + Expressions.add( + Expressions.multiply( + Expressions.convert_( + Expressions.call(BuiltInMethod.CURRENT_DATE.method, root), + long.class), + Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)), + Expressions.convert_(operand, long.class)))), + Expressions.call(BuiltInMethod.TIME_ZONE.method, root)); + break; + case TIME_WITH_LOCAL_TIME_ZONE: + convert = RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method, + Expressions.call( + BuiltInMethod.UNIX_DATE_TO_STRING.method, + Expressions.call(BuiltInMethod.CURRENT_DATE.method, root)), + operand)); + break; + case TIMESTAMP: + convert = Expressions.call( + BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method, + RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method, + operand)), + Expressions.call(BuiltInMethod.TIME_ZONE.method, root)); } break; case BOOLEAN: @@ -315,6 +450,14 @@ public class RexToLixTranslator { BuiltInMethod.UNIX_TIME_TO_STRING.method, operand)); break; + case TIME_WITH_LOCAL_TIME_ZONE: + convert = RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_STRING.method, + operand, + Expressions.call(BuiltInMethod.TIME_ZONE.method, root))); + break; case TIMESTAMP: convert = RexImpTable.optimize2( operand, @@ -322,6 +465,14 @@ public class RexToLixTranslator { BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method, operand)); break; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + convert = RexImpTable.optimize2( + operand, + Expressions.call( + BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_STRING.method, + operand, + Expressions.call(BuiltInMethod.TIME_ZONE.method, root))); + break; case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: @@ -621,6 +772,7 @@ public class RexToLixTranslator { Expressions.constant(bd.toString())); case DATE: case TIME: + case TIME_WITH_LOCAL_TIME_ZONE: case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: @@ -628,6 +780,7 @@ public class RexToLixTranslator { javaClass = int.class; break; case TIMESTAMP: + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: case INTERVAL_DAY: case INTERVAL_DAY_HOUR: case INTERVAL_DAY_MINUTE: http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java index 2e0ded2..8999f44 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java +++ b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java @@ -176,12 +176,14 @@ public class JavaTypeFactoryImpl return String.class; case DATE: case TIME: + case TIME_WITH_LOCAL_TIME_ZONE: case INTEGER: case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: return type.isNullable() ? Integer.class : int.class; case TIMESTAMP: + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: case BIGINT: case INTERVAL_DAY: case INTERVAL_DAY_HOUR: http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java index bd12347..085fd7c 100644 --- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java +++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java @@ -281,6 +281,7 @@ public class RelMdSize implements MetadataHandler<BuiltInMetadata.Size> { case DECIMAL: case DATE: case TIME: + case TIME_WITH_LOCAL_TIME_ZONE: case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: @@ -289,6 +290,7 @@ public class RelMdSize implements MetadataHandler<BuiltInMetadata.Size> { case DOUBLE: case FLOAT: // sic case TIMESTAMP: + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: case INTERVAL_DAY: case INTERVAL_DAY_HOUR: case INTERVAL_DAY_MINUTE: @@ -339,6 +341,7 @@ public class RelMdSize implements MetadataHandler<BuiltInMetadata.Size> { case REAL: case DATE: case TIME: + case TIME_WITH_LOCAL_TIME_ZONE: case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: @@ -346,6 +349,7 @@ public class RelMdSize implements MetadataHandler<BuiltInMetadata.Size> { case BIGINT: case DOUBLE: case TIMESTAMP: + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: case INTERVAL_DAY: case INTERVAL_DAY_HOUR: case INTERVAL_DAY_MINUTE: http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java index e7b4467..127e95e 100644 --- a/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java +++ b/core/src/main/java/org/apache/calcite/rel/rules/SortProjectTransposeRule.java @@ -23,6 +23,7 @@ import org.apache.calcite.plan.RelOptRuleOperand; import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.rel.RelCollation; import org.apache.calcite.rel.RelCollationTraitDef; +import org.apache.calcite.rel.RelCollations; import org.apache.calcite.rel.RelFieldCollation; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.core.Project; @@ -102,7 +103,7 @@ public class SortProjectTransposeRule extends RelOptRule { final RexCall cast = (RexCall) node; final RexCallBinding binding = RexCallBinding.create(cluster.getTypeFactory(), cast, - ImmutableList.of(RexUtil.apply(map, sort.getCollation()))); + ImmutableList.of(RelCollations.of(RexUtil.apply(map, fc)))); if (cast.getOperator().getMonotonicity(binding) == SqlMonotonicity.NOT_MONOTONIC) { return; } http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java index 3e0eebd..b7b8839 100644 --- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java +++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java @@ -97,9 +97,11 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem { case DOUBLE: return 15; case TIME: + case TIME_WITH_LOCAL_TIME_ZONE: case DATE: return 0; // SQL99 part 2 section 6.1 syntax rule 30 case TIMESTAMP: + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: // farrago supports only 0 (see // SqlTypeName.getDefaultPrecision), but it should be 6 // (microseconds) per SQL99 part 2 section 6.1 syntax rule 30. @@ -120,7 +122,9 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem { case BINARY: return 65536; case TIME: + case TIME_WITH_LOCAL_TIME_ZONE: case TIMESTAMP: + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: return SqlTypeName.MAX_DATETIME_PRECISION; case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: @@ -159,6 +163,8 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem { return isPrefix ? "x'" : "'"; case TIMESTAMP: return isPrefix ? "TIMESTAMP '" : "'"; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + return isPrefix ? "TIMESTAMP WITH LOCAL TIME ZONE '" : "'"; case INTERVAL_DAY: case INTERVAL_DAY_HOUR: case INTERVAL_DAY_MINUTE: @@ -176,6 +182,8 @@ public abstract class RelDataTypeSystemImpl implements RelDataTypeSystem { return isPrefix ? "INTERVAL '" : "' YEAR TO MONTH"; case TIME: return isPrefix ? "TIME '" : "'"; + case TIME_WITH_LOCAL_TIME_ZONE: + return isPrefix ? "TIME WITH LOCAL TIME ZONE '" : "'"; case DATE: return isPrefix ? "DATE '" : "'"; case ARRAY: http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/rex/RexBuilder.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java index adf7fee..2144ab8 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java +++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java @@ -863,6 +863,14 @@ public class RexBuilder { } o = ((TimeString) o).round(p); break; + case TIME_WITH_LOCAL_TIME_ZONE: + assert o instanceof TimeString; + p = type.getPrecision(); + if (p == RelDataType.PRECISION_NOT_SPECIFIED) { + p = 0; + } + o = ((TimeString) o).round(p); + break; case TIMESTAMP: assert o instanceof TimestampString; p = type.getPrecision(); @@ -871,6 +879,13 @@ public class RexBuilder { } o = ((TimestampString) o).round(p); break; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + assert o instanceof TimestampString; + p = type.getPrecision(); + if (p == RelDataType.PRECISION_NOT_SPECIFIED) { + p = 0; + } + o = ((TimestampString) o).round(p); } return new RexLiteral(o, type, typeName); } @@ -1084,6 +1099,17 @@ public class RexBuilder { SqlTypeName.TIME); } + /** + * Creates a Time with local time-zone literal. + */ + public RexLiteral makeTimeWithLocalTimeZoneLiteral( + TimeString time, + int precision) { + return makeLiteral(Preconditions.checkNotNull(time), + typeFactory.createSqlType(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE, precision), + SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE); + } + /** @deprecated Use {@link #makeTimestampLiteral(TimestampString, int)}. */ @Deprecated // to be removed before 2.0 public RexLiteral makeTimestampLiteral(Calendar calendar, int precision) { @@ -1102,6 +1128,17 @@ public class RexBuilder { } /** + * Creates a Timestamp with local time-zone literal. + */ + public RexLiteral makeTimestampWithLocalTimeZoneLiteral( + TimestampString timestamp, + int precision) { + return makeLiteral(Preconditions.checkNotNull(timestamp), + typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, precision), + SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE); + } + + /** * Creates a literal representing an interval type, for example * {@code YEAR TO MONTH} or {@code DOW}. */ @@ -1225,6 +1262,10 @@ public class RexBuilder { case DATE: case TIMESTAMP: return DateTimeUtils.ZERO_CALENDAR; + case TIME_WITH_LOCAL_TIME_ZONE: + return new TimeString(0, 0, 0); + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + return new TimestampString(0, 0, 0, 0, 0, 0); default: throw Util.unexpected(type.getSqlTypeName()); } @@ -1288,10 +1329,14 @@ public class RexBuilder { return (Boolean) value ? booleanTrue : booleanFalse; case TIME: return makeTimeLiteral((TimeString) value, type.getPrecision()); + case TIME_WITH_LOCAL_TIME_ZONE: + return makeTimeWithLocalTimeZoneLiteral((TimeString) value, type.getPrecision()); case DATE: return makeDateLiteral((DateString) value); case TIMESTAMP: return makeTimestampLiteral((TimestampString) value, type.getPrecision()); + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + return makeTimestampWithLocalTimeZoneLiteral((TimestampString) value, type.getPrecision()); case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: @@ -1423,6 +1468,12 @@ public class RexBuilder { } else { return TimeString.fromMillisOfDay((Integer) o); } + case TIME_WITH_LOCAL_TIME_ZONE: + if (o instanceof TimeString) { + return o; + } else { + return TimeString.fromMillisOfDay((Integer) o); + } case DATE: if (o instanceof DateString) { return o; @@ -1445,6 +1496,12 @@ public class RexBuilder { } else { return TimestampString.fromMillisSinceEpoch((Long) o); } + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + if (o instanceof TimestampString) { + return o; + } else { + return TimestampString.fromMillisSinceEpoch((Long) o); + } default: return o; } http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/rex/RexLiteral.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java index 8c4c732..a5ed918 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java +++ b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java @@ -255,8 +255,12 @@ public class RexLiteral extends RexNode { return value instanceof DateString; case TIME: return value instanceof TimeString; + case TIME_WITH_LOCAL_TIME_ZONE: + return value instanceof TimeString; case TIMESTAMP: return value instanceof TimestampString; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + return value instanceof TimestampString; case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: @@ -498,10 +502,18 @@ public class RexLiteral extends RexNode { assert value instanceof TimeString; pw.print(value); break; + case TIME_WITH_LOCAL_TIME_ZONE: + assert value instanceof TimeString; + pw.print(value); + break; case TIMESTAMP: assert value instanceof TimestampString; pw.print(value); break; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + assert value instanceof TimestampString; + pw.print(value); + break; case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: @@ -724,9 +736,11 @@ public class RexLiteral extends RexNode { return getValueAs(String.class); case DECIMAL: case TIMESTAMP: + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: return getValueAs(Long.class); case DATE: case TIME: + case TIME_WITH_LOCAL_TIME_ZONE: return getValueAs(Integer.class); default: return value; @@ -835,6 +849,12 @@ public class RexLiteral extends RexNode { return clazz.cast(((TimeString) value).toCalendar()); } break; + case TIME_WITH_LOCAL_TIME_ZONE: + if (clazz == Integer.class) { + // Milliseconds since 1970-01-01 00:00:00 + return clazz.cast(((TimeString) value).getMillisOfDay()); + } + break; case TIMESTAMP: if (clazz == Long.class) { // Milliseconds since 1970-01-01 00:00:00 @@ -844,6 +864,12 @@ public class RexLiteral extends RexNode { return clazz.cast(((TimestampString) value).toCalendar()); } break; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + if (clazz == Long.class) { + // Milliseconds since 1970-01-01 00:00:00 + return clazz.cast(((TimestampString) value).getMillisSinceEpoch()); + } + break; case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/rex/RexSimplify.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java index 67ac1b8..52c4795 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java +++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java @@ -811,6 +811,7 @@ public class RexSimplify { case TIMESTAMP: return e; } + break; } final List<RexNode> reducedValues = new ArrayList<>(); executor.reduce(rexBuilder, ImmutableList.<RexNode>of(e), reducedValues); http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java index 6832ee4..736034c 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -33,6 +33,8 @@ import org.apache.calcite.linq4j.tree.Primitive; import org.apache.calcite.runtime.FlatLists.ComparableList; import org.apache.calcite.util.Bug; import org.apache.calcite.util.NumberUtil; +import org.apache.calcite.util.TimeWithTimeZoneString; +import org.apache.calcite.util.TimestampWithTimeZoneString; import java.math.BigDecimal; import java.math.BigInteger; @@ -1695,6 +1697,50 @@ public class SqlFunctions { return v == null ? null : internalToTime(v.intValue()); } + public static Integer toTimeWithLocalTimeZone(String v) { + return v == null ? null : new TimeWithTimeZoneString(v) + .withTimeZone(DateTimeUtils.UTC_ZONE) + .getLocalTimeString() + .getMillisOfDay(); + } + + public static Integer toTimeWithLocalTimeZone(String v, TimeZone timeZone) { + return v == null ? null : new TimeWithTimeZoneString(v + " " + timeZone.getID()) + .withTimeZone(DateTimeUtils.UTC_ZONE) + .getLocalTimeString() + .getMillisOfDay(); + } + + public static int timeWithLocalTimeZoneToTime(int v, TimeZone timeZone) { + return TimeWithTimeZoneString.fromMillisOfDay(v) + .withTimeZone(timeZone) + .getLocalTimeString() + .getMillisOfDay(); + } + + public static long timeWithLocalTimeZoneToTimestamp(String date, int v, TimeZone timeZone) { + final TimeWithTimeZoneString tTZ = TimeWithTimeZoneString.fromMillisOfDay(v) + .withTimeZone(DateTimeUtils.UTC_ZONE); + return new TimestampWithTimeZoneString(date + " " + tTZ.toString()) + .withTimeZone(timeZone) + .getLocalTimestampString() + .getMillisSinceEpoch(); + } + + public static long timeWithLocalTimeZoneToTimestampWithLocalTimeZone(String date, int v) { + final TimeWithTimeZoneString tTZ = TimeWithTimeZoneString.fromMillisOfDay(v) + .withTimeZone(DateTimeUtils.UTC_ZONE); + return new TimestampWithTimeZoneString(date + " " + tTZ.toString()) + .getLocalTimestampString() + .getMillisSinceEpoch(); + } + + public static String timeWithLocalTimeZoneToString(int v, TimeZone timeZone) { + return TimeWithTimeZoneString.fromMillisOfDay(v) + .withTimeZone(timeZone) + .toString(); + } + /** Converts the internal representation of a SQL TIMESTAMP (long) to the Java * type used for UDF parameters ({@link java.sql.Timestamp}). */ public static java.sql.Timestamp internalToTimestamp(long v) { @@ -1705,6 +1751,53 @@ public class SqlFunctions { return v == null ? null : internalToTimestamp(v.longValue()); } + public static int timestampWithLocalTimeZoneToDate(long v, TimeZone timeZone) { + return TimestampWithTimeZoneString.fromMillisSinceEpoch(v) + .withTimeZone(timeZone) + .getLocalDateString() + .getDaysSinceEpoch(); + } + + public static int timestampWithLocalTimeZoneToTime(long v, TimeZone timeZone) { + return TimestampWithTimeZoneString.fromMillisSinceEpoch(v) + .withTimeZone(timeZone) + .getLocalTimeString() + .getMillisOfDay(); + } + + public static long timestampWithLocalTimeZoneToTimestamp(long v, TimeZone timeZone) { + return TimestampWithTimeZoneString.fromMillisSinceEpoch(v) + .withTimeZone(timeZone) + .getLocalTimestampString() + .getMillisSinceEpoch(); + } + + public static String timestampWithLocalTimeZoneToString(long v, TimeZone timeZone) { + return TimestampWithTimeZoneString.fromMillisSinceEpoch(v) + .withTimeZone(timeZone) + .toString(); + } + + public static int timestampWithLocalTimeZoneToTimeWithLocalTimeZone(long v) { + return TimestampWithTimeZoneString.fromMillisSinceEpoch(v) + .getLocalTimeString() + .getMillisOfDay(); + } + + public static Long toTimestampWithLocalTimeZone(String v) { + return v == null ? null : new TimestampWithTimeZoneString(v) + .withTimeZone(DateTimeUtils.UTC_ZONE) + .getLocalTimestampString() + .getMillisSinceEpoch(); + } + + public static Long toTimestampWithLocalTimeZone(String v, TimeZone timeZone) { + return v == null ? null : new TimestampWithTimeZoneString(v + " " + timeZone.getID()) + .withTimeZone(DateTimeUtils.UTC_ZONE) + .getLocalTimestampString() + .getMillisSinceEpoch(); + } + // Don't need shortValueOf etc. - Short.valueOf is sufficient. /** Helper for CAST(... AS VARCHAR(maxLength)). */ @@ -1867,6 +1960,11 @@ public class SqlFunctions { return (int) (localTimestamp(root) % DateTimeUtils.MILLIS_PER_DAY); } + @NonDeterministic + public static TimeZone timeZone(DataContext root) { + return (TimeZone) DataContext.Variable.TIME_ZONE.get(root); + } + /** SQL {@code TRANSLATE(string, search_chars, replacement_chars)} * function. */ public static String translate3(String s, String search, String replacement) { http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/schema/Schemas.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/schema/Schemas.java b/core/src/main/java/org/apache/calcite/schema/Schemas.java index edb833a..6466d31 100644 --- a/core/src/main/java/org/apache/calcite/schema/Schemas.java +++ b/core/src/main/java/org/apache/calcite/schema/Schemas.java @@ -54,7 +54,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.TimeZone; /** * Utility functions for schemas. @@ -564,8 +563,7 @@ public final class Schemas { DummyDataContext(CalciteConnection connection, SchemaPlus rootSchema) { this.connection = connection; this.rootSchema = rootSchema; - this.map = - ImmutableMap.<String, Object>of("timeZone", TimeZone.getDefault()); + this.map = ImmutableMap.<String, Object>of(); } public SchemaPlus getRootSchema() { http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/sql/SqlJdbcDataTypeName.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/SqlJdbcDataTypeName.java b/core/src/main/java/org/apache/calcite/sql/SqlJdbcDataTypeName.java index 90b0597..2f9d974 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlJdbcDataTypeName.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlJdbcDataTypeName.java @@ -34,7 +34,9 @@ public enum SqlJdbcDataTypeName { SQL_VARCHAR(SqlTypeName.VARCHAR), SQL_DATE(SqlTypeName.DATE), SQL_TIME(SqlTypeName.TIME), + SQL_TIME_WITH_LOCAL_TIME_ZONE(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE), SQL_TIMESTAMP(SqlTypeName.TIMESTAMP), + SQL_TIMESTAMP_WITH_LOCAL_TIME_ZONE(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE), SQL_DECIMAL(SqlTypeName.DECIMAL), SQL_NUMERIC(SqlTypeName.DECIMAL), SQL_BOOLEAN(SqlTypeName.BOOLEAN), http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java index f77d639..cd46c39 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java +++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRules.java @@ -158,9 +158,17 @@ public class SqlTypeAssignmentRules { rule.add(SqlTypeName.TIMESTAMP); rules.put(SqlTypeName.TIME, rule); + // Time with local time-zone is assignable from ... + rules.put(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE, + EnumSet.of(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE)); + // Timestamp is assignable from ... rules.put(SqlTypeName.TIMESTAMP, EnumSet.of(SqlTypeName.TIMESTAMP)); + // Timestamp with local time-zone is assignable from ... + rules.put(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, + EnumSet.of(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)); + // Geometry is assignable from ... rules.put(SqlTypeName.GEOMETRY, EnumSet.of(SqlTypeName.GEOMETRY)); @@ -275,6 +283,7 @@ public class SqlTypeAssignmentRules { rule = new HashSet<>(); rule.add(SqlTypeName.DATE); rule.add(SqlTypeName.TIMESTAMP); + rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE); rule.add(SqlTypeName.CHAR); rule.add(SqlTypeName.VARCHAR); coerceRules.put(SqlTypeName.DATE, rule); @@ -282,19 +291,44 @@ public class SqlTypeAssignmentRules { // Time is castable from ... rule = new HashSet<>(); rule.add(SqlTypeName.TIME); + rule.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE); rule.add(SqlTypeName.TIMESTAMP); + rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE); rule.add(SqlTypeName.CHAR); rule.add(SqlTypeName.VARCHAR); coerceRules.put(SqlTypeName.TIME, rule); + // Time with local time-zone is castable from ... + rule = new HashSet<>(); + rule.add(SqlTypeName.TIME); + rule.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE); + rule.add(SqlTypeName.TIMESTAMP); + rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE); + rule.add(SqlTypeName.CHAR); + rule.add(SqlTypeName.VARCHAR); + coerceRules.put(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE, rule); + // Timestamp is castable from ... rule = new HashSet<>(); rule.add(SqlTypeName.TIMESTAMP); + rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE); rule.add(SqlTypeName.DATE); rule.add(SqlTypeName.TIME); + rule.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE); rule.add(SqlTypeName.CHAR); rule.add(SqlTypeName.VARCHAR); coerceRules.put(SqlTypeName.TIMESTAMP, rule); + + // Timestamp with local time-zone is castable from ... + rule = new HashSet<>(); + rule.add(SqlTypeName.TIMESTAMP); + rule.add(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE); + rule.add(SqlTypeName.DATE); + rule.add(SqlTypeName.TIME); + rule.add(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE); + rule.add(SqlTypeName.CHAR); + rule.add(SqlTypeName.VARCHAR); + coerceRules.put(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, rule); } //~ Methods ---------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java index e88f96a..af1733b 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java +++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java @@ -99,7 +99,9 @@ public enum SqlTypeFamily implements RelDataTypeFamily { .put(Types.DATE, DATE) .put(Types.TIME, TIME) + .put(Types.TIME_WITH_TIMEZONE, TIME) .put(Types.TIMESTAMP, TIMESTAMP) + .put(Types.TIMESTAMP_WITH_TIMEZONE, TIMESTAMP) .put(Types.BOOLEAN, BOOLEAN) .put(ExtraSqlTypes.REF_CURSOR, CURSOR) @@ -130,9 +132,9 @@ public enum SqlTypeFamily implements RelDataTypeFamily { case DATE: return ImmutableList.of(SqlTypeName.DATE); case TIME: - return ImmutableList.of(SqlTypeName.TIME); + return ImmutableList.of(SqlTypeName.TIME, SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE); case TIMESTAMP: - return ImmutableList.of(SqlTypeName.TIMESTAMP); + return ImmutableList.of(SqlTypeName.TIMESTAMP, SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE); case BOOLEAN: return SqlTypeName.BOOLEAN_TYPES; case INTERVAL_YEAR_MONTH: http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java index fcae1ec..e56b9cf 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java +++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java @@ -63,8 +63,12 @@ public enum SqlTypeName { DATE(PrecScale.NO_NO, false, Types.DATE, SqlTypeFamily.DATE), TIME(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.TIME, SqlTypeFamily.TIME), + TIME_WITH_LOCAL_TIME_ZONE(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.OTHER, + SqlTypeFamily.TIME), TIMESTAMP(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.TIMESTAMP, SqlTypeFamily.TIMESTAMP), + TIMESTAMP_WITH_LOCAL_TIME_ZONE(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.OTHER, + SqlTypeFamily.TIMESTAMP), INTERVAL_YEAR(PrecScale.NO_NO, false, Types.OTHER, SqlTypeFamily.INTERVAL_YEAR_MONTH), INTERVAL_YEAR_MONTH(PrecScale.NO_NO, false, Types.OTHER, @@ -147,7 +151,7 @@ public enum SqlTypeName { INTERVAL_DAY, INTERVAL_DAY_HOUR, INTERVAL_DAY_MINUTE, INTERVAL_DAY_SECOND, INTERVAL_HOUR, INTERVAL_HOUR_MINUTE, INTERVAL_HOUR_SECOND, INTERVAL_MINUTE, INTERVAL_MINUTE_SECOND, - INTERVAL_SECOND, + INTERVAL_SECOND, TIME_WITH_LOCAL_TIME_ZONE, TIMESTAMP_WITH_LOCAL_TIME_ZONE, FLOAT, MULTISET, DISTINCT, STRUCTURED, ROW, CURSOR, COLUMN_LIST); public static final List<SqlTypeName> BOOLEAN_TYPES = @@ -178,7 +182,8 @@ public enum SqlTypeName { combine(CHAR_TYPES, BINARY_TYPES); public static final List<SqlTypeName> DATETIME_TYPES = - ImmutableList.of(DATE, TIME, TIMESTAMP); + ImmutableList.of(DATE, TIME, TIME_WITH_LOCAL_TIME_ZONE, + TIMESTAMP, TIMESTAMP_WITH_LOCAL_TIME_ZONE); public static final Set<SqlTypeName> YEAR_INTERVAL_TYPES = Sets.immutableEnumSet(SqlTypeName.INTERVAL_YEAR, @@ -740,7 +745,9 @@ public enum SqlTypeName { case VARBINARY: case BINARY: case TIME: + case TIME_WITH_LOCAL_TIME_ZONE: case TIMESTAMP: + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: return 1; case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java index 8940629..14bff94 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java @@ -678,6 +678,14 @@ public class StandardConvertletTable extends ReflectiveConvertletTable { case INTERVAL_MINUTE_SECOND: case INTERVAL_SECOND: break; + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + RelDataType type = + cx.getTypeFactory().createSqlType(SqlTypeName.TIMESTAMP); + type = cx.getTypeFactory().createTypeWithNullability( + type, + exprs.get(1).getType().isNullable()); + res = rexBuilder.makeCast(type, res); + // fall through case TIMESTAMP: res = divide(rexBuilder, res, TimeUnit.DAY.multiplier); // fall through @@ -690,6 +698,14 @@ public class StandardConvertletTable extends ReflectiveConvertletTable { break; case DECADE: switch (sqlTypeName) { + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + RelDataType type = + cx.getTypeFactory().createSqlType(SqlTypeName.TIMESTAMP); + type = cx.getTypeFactory().createTypeWithNullability( + type, + exprs.get(1).getType().isNullable()); + res = rexBuilder.makeCast(type, res); + // fall through case TIMESTAMP: res = divide(rexBuilder, res, TimeUnit.DAY.multiplier); // fall through @@ -709,6 +725,16 @@ public class StandardConvertletTable extends ReflectiveConvertletTable { case TIMESTAMP: // convert to seconds return divide(rexBuilder, res, TimeUnit.SECOND.multiplier); + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + RelDataType type = + cx.getTypeFactory().createSqlType(SqlTypeName.TIMESTAMP); + type = cx.getTypeFactory().createTypeWithNullability( + type, + exprs.get(1).getType().isNullable()); + return divide( + rexBuilder, + rexBuilder.makeCast(type, res), + TimeUnit.SECOND.multiplier); case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java index 3265db5..648f1b0 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -297,8 +297,33 @@ public enum BuiltInMethod { INTERNAL_TO_TIMESTAMP(SqlFunctions.class, "internalToTimestamp", long.class), STRING_TO_DATE(DateTimeUtils.class, "dateStringToUnixDate", String.class), STRING_TO_TIME(DateTimeUtils.class, "timeStringToUnixDate", String.class), - STRING_TO_TIMESTAMP(DateTimeUtils.class, "timestampStringToUnixDate", + STRING_TO_TIMESTAMP(DateTimeUtils.class, "timestampStringToUnixDate", String.class), + STRING_TO_TIME_WITH_LOCAL_TIME_ZONE(SqlFunctions.class, "toTimeWithLocalTimeZone", String.class), + TIME_STRING_TO_TIME_WITH_LOCAL_TIME_ZONE(SqlFunctions.class, "toTimeWithLocalTimeZone", + String.class, TimeZone.class), + STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE(SqlFunctions.class, "toTimestampWithLocalTimeZone", + String.class), + TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE(SqlFunctions.class, + "toTimestampWithLocalTimeZone", String.class, TimeZone.class), + TIME_WITH_LOCAL_TIME_ZONE_TO_TIME(SqlFunctions.class, "timeWithLocalTimeZoneToTime", + int.class, TimeZone.class), + TIME_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP(SqlFunctions.class, "timeWithLocalTimeZoneToTimestamp", + String.class, int.class, TimeZone.class), + TIME_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE(SqlFunctions.class, + "timeWithLocalTimeZoneToTimestampWithLocalTimeZone", String.class, int.class), + TIME_WITH_LOCAL_TIME_ZONE_TO_STRING(SqlFunctions.class, "timeWithLocalTimeZoneToString", + int.class, TimeZone.class), + TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_DATE(SqlFunctions.class, "timestampWithLocalTimeZoneToDate", + long.class, TimeZone.class), + TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME(SqlFunctions.class, "timestampWithLocalTimeZoneToTime", + long.class, TimeZone.class), + TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME_WITH_LOCAL_TIME_ZONE(SqlFunctions.class, + "timestampWithLocalTimeZoneToTimeWithLocalTimeZone", long.class), + TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP(SqlFunctions.class, + "timestampWithLocalTimeZoneToTimestamp", long.class, TimeZone.class), + TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_STRING(SqlFunctions.class, + "timestampWithLocalTimeZoneToString", long.class, TimeZone.class), UNIX_DATE_TO_STRING(DateTimeUtils.class, "unixDateToString", int.class), UNIX_TIME_TO_STRING(DateTimeUtils.class, "unixTimeToString", int.class), UNIX_TIMESTAMP_TO_STRING(DateTimeUtils.class, "unixTimestampToString", @@ -322,6 +347,7 @@ public enum BuiltInMethod { CURRENT_DATE(SqlFunctions.class, "currentDate", DataContext.class), LOCAL_TIMESTAMP(SqlFunctions.class, "localTimestamp", DataContext.class), LOCAL_TIME(SqlFunctions.class, "localTime", DataContext.class), + TIME_ZONE(SqlFunctions.class, "timeZone", DataContext.class), BOOLEAN_TO_STRING(SqlFunctions.class, "toString", boolean.class), JDBC_ARRAY_TO_LIST(SqlFunctions.class, "arrayToList", java.sql.Array.class), OBJECT_TO_STRING(Object.class, "toString"), http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/util/DateString.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/util/DateString.java b/core/src/main/java/org/apache/calcite/util/DateString.java index cea9df3..5aaa1b8 100644 --- a/core/src/main/java/org/apache/calcite/util/DateString.java +++ b/core/src/main/java/org/apache/calcite/util/DateString.java @@ -42,7 +42,7 @@ public class DateString implements Comparable<DateString> { /** Creates a DateString for year, month, day values. */ public DateString(int year, int month, int day) { - this(TimestampString.ymd(new StringBuilder(), year, month, day).toString()); + this(DateTimeStringUtils.ymd(new StringBuilder(), year, month, day).toString()); } @Override public String toString() { http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java b/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java new file mode 100644 index 0000000..c0d38df --- /dev/null +++ b/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.util; + +import java.util.TimeZone; + +/** + * Utility methods to manipulate String representation of DateTime values. + */ +public class DateTimeStringUtils { + + private DateTimeStringUtils() {} + + static String pad(int length, long v) { + StringBuilder s = new StringBuilder(Long.toString(v)); + while (s.length() < length) { + s.insert(0, "0"); + } + return s.toString(); + } + + static StringBuilder hms(StringBuilder b, int h, int m, int s) { + int2(b, h); + b.append(':'); + int2(b, m); + b.append(':'); + int2(b, s); + return b; + } + + static StringBuilder ymdhms(StringBuilder b, int year, int month, int day, + int h, int m, int s) { + ymd(b, year, month, day); + b.append(' '); + hms(b, h, m, s); + return b; + } + + static StringBuilder ymd(StringBuilder b, int year, int month, int day) { + int4(b, year); + b.append('-'); + int2(b, month); + b.append('-'); + int2(b, day); + return b; + } + + private static void int4(StringBuilder buf, int i) { + buf.append((char) ('0' + (i / 1000) % 10)); + buf.append((char) ('0' + (i / 100) % 10)); + buf.append((char) ('0' + (i / 10) % 10)); + buf.append((char) ('0' + i % 10)); + } + + private static void int2(StringBuilder buf, int i) { + buf.append((char) ('0' + (i / 10) % 10)); + buf.append((char) ('0' + i % 10)); + } + + static boolean isValidTimeZone(final String timeZone) { + if (timeZone.equals("GMT")) { + return true; + } else { + String id = TimeZone.getTimeZone(timeZone).getID(); + if (!id.equals("GMT")) { + return true; + } + } + return false; + } + +} + +// End DateTimeStringUtils.java http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/util/TimeString.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/util/TimeString.java b/core/src/main/java/org/apache/calcite/util/TimeString.java index 75aa96b..ce03d87 100644 --- a/core/src/main/java/org/apache/calcite/util/TimeString.java +++ b/core/src/main/java/org/apache/calcite/util/TimeString.java @@ -44,7 +44,7 @@ public class TimeString implements Comparable<TimeString> { /** Creates a TimeString for hour, minute, second and millisecond values. */ public TimeString(int h, int m, int s) { - this(TimestampString.hms(new StringBuilder(), h, m, s).toString()); + this(DateTimeStringUtils.hms(new StringBuilder(), h, m, s).toString()); } /** Sets the fraction field of a {@code TimeString} to a given number @@ -55,7 +55,7 @@ public class TimeString implements Comparable<TimeString> { * yields {@code TIME '1970-01-01 02:03:04.056'}. */ public TimeString withMillis(int millis) { Preconditions.checkArgument(millis >= 0 && millis < 1000); - return withFraction(TimestampString.pad(3, millis)); + return withFraction(DateTimeStringUtils.pad(3, millis)); } /** Sets the fraction field of a {@code TimeString} to a given number @@ -66,7 +66,7 @@ public class TimeString implements Comparable<TimeString> { * yields {@code TIME '1970-01-01 02:03:04.000056789'}. */ public TimeString withNanos(int nanos) { Preconditions.checkArgument(nanos >= 0 && nanos < 1000000000); - return withFraction(TimestampString.pad(9, nanos)); + return withFraction(DateTimeStringUtils.pad(9, nanos)); } /** Sets the fraction field of a {@code TimeString}. http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/util/TimeWithTimeZoneString.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/util/TimeWithTimeZoneString.java b/core/src/main/java/org/apache/calcite/util/TimeWithTimeZoneString.java new file mode 100644 index 0000000..6547f06 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/util/TimeWithTimeZoneString.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.util; + +import org.apache.calcite.avatica.util.DateTimeUtils; + +import com.google.common.base.Preconditions; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Time with time-zone literal. + * + * <p>Immutable, internally represented as a string (in ISO format), + * and can support unlimited precision (milliseconds, nanoseconds). + */ +public class TimeWithTimeZoneString implements Comparable<TimeWithTimeZoneString> { + + final TimeString localTime; + final TimeZone timeZone; + final String v; + + /** Creates a TimeWithTimeZoneString. */ + public TimeWithTimeZoneString(TimeString localTime, TimeZone timeZone) { + this.localTime = localTime; + this.timeZone = timeZone; + this.v = localTime.toString() + " " + timeZone.getID(); + } + + /** Creates a TimeWithTimeZoneString. */ + public TimeWithTimeZoneString(String v) { + this.localTime = new TimeString(v.substring(0, 8)); + String timeZoneString = v.substring(9); + Preconditions.checkArgument(DateTimeStringUtils.isValidTimeZone(timeZoneString)); + this.timeZone = TimeZone.getTimeZone(timeZoneString); + this.v = v; + } + + /** Creates a TimeWithTimeZoneString for hour, minute, second and millisecond values + * in the given time-zone. */ + public TimeWithTimeZoneString(int h, int m, int s, String timeZone) { + this(DateTimeStringUtils.hms(new StringBuilder(), h, m, s).toString() + " " + timeZone); + } + + /** Sets the fraction field of a {@code TimeWithTimeZoneString} to a given number + * of milliseconds. Nukes the value set via {@link #withNanos}. + * + * <p>For example, + * {@code new TimeWithTimeZoneString(1970, 1, 1, 2, 3, 4, "UTC").withMillis(56)} + * yields {@code TIME WITH LOCAL TIME ZONE '1970-01-01 02:03:04.056 UTC'}. */ + public TimeWithTimeZoneString withMillis(int millis) { + Preconditions.checkArgument(millis >= 0 && millis < 1000); + return withFraction(DateTimeStringUtils.pad(3, millis)); + } + + /** Sets the fraction field of a {@code TimeString} to a given number + * of nanoseconds. Nukes the value set via {@link #withMillis(int)}. + * + * <p>For example, + * {@code new TimeWithTimeZoneString(1970, 1, 1, 2, 3, 4, "UTC").withNanos(56789)} + * yields {@code TIME WITH LOCAL TIME ZONE '1970-01-01 02:03:04.000056789 UTC'}. */ + public TimeWithTimeZoneString withNanos(int nanos) { + Preconditions.checkArgument(nanos >= 0 && nanos < 1000000000); + return withFraction(DateTimeStringUtils.pad(9, nanos)); + } + + /** Sets the fraction field of a {@code TimeWithTimeZoneString}. + * The precision is determined by the number of leading zeros. + * Trailing zeros are stripped. + * + * <p>For example, + * {@code new TimeWithTimeZoneString(1970, 1, 1, 2, 3, 4, "UTC").withFraction("00506000")} + * yields {@code TIME WITH LOCAL TIME ZONE '1970-01-01 02:03:04.00506 UTC'}. */ + public TimeWithTimeZoneString withFraction(String fraction) { + String v = this.v; + int i = v.indexOf('.'); + if (i >= 0) { + v = v.substring(0, i); + } else { + v = v.substring(0, 8); + } + while (fraction.endsWith("0")) { + fraction = fraction.substring(0, fraction.length() - 1); + } + if (fraction.length() > 0) { + v = v + "." + fraction; + } + v = v + this.v.substring(8); // time-zone + return new TimeWithTimeZoneString(v); + } + + public TimeWithTimeZoneString withTimeZone(TimeZone timeZone) { + if (this.timeZone.equals(timeZone)) { + return this; + } + String localTimeString = localTime.toString(); + String v; + String fraction; + int i = localTimeString.indexOf('.'); + if (i >= 0) { + v = localTimeString.substring(0, i); + fraction = localTimeString.substring(i + 1); + } else { + v = localTimeString; + fraction = null; + } + final DateTimeUtils.PrecisionTime pt = + DateTimeUtils.parsePrecisionDateTimeLiteral(v, + new SimpleDateFormat(DateTimeUtils.TIME_FORMAT_STRING, Locale.ROOT), + this.timeZone, -1); + pt.getCalendar().setTimeZone(timeZone); + if (fraction != null) { + return new TimeWithTimeZoneString( + pt.getCalendar().get(Calendar.HOUR_OF_DAY), + pt.getCalendar().get(Calendar.MINUTE), + pt.getCalendar().get(Calendar.SECOND), + timeZone.getID()) + .withFraction(fraction); + } + return new TimeWithTimeZoneString( + pt.getCalendar().get(Calendar.HOUR_OF_DAY), + pt.getCalendar().get(Calendar.MINUTE), + pt.getCalendar().get(Calendar.SECOND), + timeZone.getID()); + } + + @Override public String toString() { + return v; + } + + @Override public boolean equals(Object o) { + // The value is in canonical form (no trailing zeros). + return o == this + || o instanceof TimeWithTimeZoneString + && ((TimeWithTimeZoneString) o).v.equals(v); + } + + @Override public int hashCode() { + return v.hashCode(); + } + + @Override public int compareTo(TimeWithTimeZoneString o) { + return v.compareTo(o.v); + } + + public TimeWithTimeZoneString round(int precision) { + Preconditions.checkArgument(precision >= 0); + return new TimeWithTimeZoneString( + localTime.round(precision), timeZone); + } + + public static TimeWithTimeZoneString fromMillisOfDay(int i) { + return new TimeWithTimeZoneString( + DateTimeUtils.unixTimeToString(i) + " " + DateTimeUtils.UTC_ZONE.getID()) + .withMillis((int) DateTimeUtils.floorMod(i, 1000)); + } + + /** Converts this TimeWithTimeZoneString to a string, truncated or padded with + * zeroes to a given precision. */ + public String toString(int precision) { + Preconditions.checkArgument(precision >= 0); + return localTime.toString(precision) + " " + timeZone.getID(); + } + + public TimeString getLocalTimeString() { + return localTime; + } + +} + +// End TimeWithTimeZoneString.java http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/util/TimestampString.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/util/TimestampString.java b/core/src/main/java/org/apache/calcite/util/TimestampString.java index 4e392f0..604d5a4 100644 --- a/core/src/main/java/org/apache/calcite/util/TimestampString.java +++ b/core/src/main/java/org/apache/calcite/util/TimestampString.java @@ -47,7 +47,7 @@ public class TimestampString implements Comparable<TimestampString> { /** Creates a TimestampString for year, month, day, hour, minute, second, * millisecond values. */ public TimestampString(int year, int month, int day, int h, int m, int s) { - this(ymdhms(new StringBuilder(), year, month, day, h, m, s).toString()); + this(DateTimeStringUtils.ymdhms(new StringBuilder(), year, month, day, h, m, s).toString()); } /** Sets the fraction field of a {@code TimestampString} to a given number @@ -58,7 +58,7 @@ public class TimestampString implements Comparable<TimestampString> { * yields {@code TIMESTAMP '1970-01-01 02:03:04.056'}. */ public TimestampString withMillis(int millis) { Preconditions.checkArgument(millis >= 0 && millis < 1000); - return withFraction(pad(3, millis)); + return withFraction(DateTimeStringUtils.pad(3, millis)); } /** Sets the fraction field of a {@code TimestampString} to a given number @@ -69,7 +69,7 @@ public class TimestampString implements Comparable<TimestampString> { * yields {@code TIMESTAMP '1970-01-01 02:03:04.000056789'}. */ public TimestampString withNanos(int nanos) { Preconditions.checkArgument(nanos >= 0 && nanos < 1000000000); - return withFraction(pad(9, nanos)); + return withFraction(DateTimeStringUtils.pad(9, nanos)); } /** Sets the fraction field of a {@code TimestampString}. @@ -113,44 +113,6 @@ public class TimestampString implements Comparable<TimestampString> { return v.compareTo(o.v); } - static StringBuilder hms(StringBuilder b, int h, int m, int s) { - int2(b, h); - b.append(':'); - int2(b, m); - b.append(':'); - int2(b, s); - return b; - } - - static StringBuilder ymdhms(StringBuilder b, int year, int month, int day, - int h, int m, int s) { - ymd(b, year, month, day); - b.append(' '); - hms(b, h, m, s); - return b; - } - - static StringBuilder ymd(StringBuilder b, int year, int month, int day) { - int4(b, year); - b.append('-'); - int2(b, month); - b.append('-'); - int2(b, day); - return b; - } - - private static void int4(StringBuilder buf, int i) { - buf.append((char) ('0' + (i / 1000) % 10)); - buf.append((char) ('0' + (i / 100) % 10)); - buf.append((char) ('0' + (i / 10) % 10)); - buf.append((char) ('0' + i % 10)); - } - - private static void int2(StringBuilder buf, int i) { - buf.append((char) ('0' + (i / 10) % 10)); - buf.append((char) ('0' + i % 10)); - } - /** Creates a TimestampString from a Calendar. */ public static TimestampString fromCalendarFields(Calendar calendar) { return new TimestampString( @@ -214,14 +176,6 @@ public class TimestampString implements Comparable<TimestampString> { .withMillis((int) DateTimeUtils.floorMod(millis, 1000)); } - static String pad(int length, long v) { - StringBuilder s = new StringBuilder(Long.toString(v)); - while (s.length() < length) { - s.insert(0, "0"); - } - return s.toString(); - } - public Calendar toCalendar() { return Util.calendar(getMillisSinceEpoch()); } http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java b/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java new file mode 100644 index 0000000..3f32762 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/util/TimestampWithTimeZoneString.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.util; + +import org.apache.calcite.avatica.util.DateTimeUtils; + +import com.google.common.base.Preconditions; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Timestamp with time-zone literal. + * + * <p>Immutable, internally represented as a string (in ISO format), + * and can support unlimited precision (milliseconds, nanoseconds). + */ +public class TimestampWithTimeZoneString + implements Comparable<TimestampWithTimeZoneString> { + + final TimestampString localDateTime; + final TimeZone timeZone; + final String v; + + /** Creates a TimestampWithTimeZoneString. */ + public TimestampWithTimeZoneString(TimestampString localDateTime, TimeZone timeZone) { + this.localDateTime = localDateTime; + this.timeZone = timeZone; + this.v = localDateTime.toString() + " " + timeZone.getID(); + } + + /** Creates a TimestampWithTimeZoneString. */ + public TimestampWithTimeZoneString(String v) { + this.localDateTime = new TimestampString(v.substring(0, v.indexOf(' ', 11))); + String timeZoneString = v.substring(v.indexOf(' ', 11) + 1); + Preconditions.checkArgument(DateTimeStringUtils.isValidTimeZone(timeZoneString)); + this.timeZone = TimeZone.getTimeZone(timeZoneString); + this.v = v; + } + + /** Creates a TimestampWithTimeZoneString for year, month, day, hour, minute, second, + * millisecond values in the given time-zone. */ + public TimestampWithTimeZoneString(int year, int month, int day, int h, int m, int s, + String timeZone) { + this(DateTimeStringUtils.ymdhms(new StringBuilder(), year, month, day, h, m, s).toString() + + " " + timeZone); + } + + /** Sets the fraction field of a {@code TimestampWithTimeZoneString} to a given number + * of milliseconds. Nukes the value set via {@link #withNanos}. + * + * <p>For example, + * {@code new TimestampWithTimeZoneString(1970, 1, 1, 2, 3, 4, "GMT").withMillis(56)} + * yields {@code TIMESTAMP WITH LOCAL TIME ZONE '1970-01-01 02:03:04.056 GMT'}. */ + public TimestampWithTimeZoneString withMillis(int millis) { + Preconditions.checkArgument(millis >= 0 && millis < 1000); + return withFraction(DateTimeStringUtils.pad(3, millis)); + } + + /** Sets the fraction field of a {@code TimestampWithTimeZoneString} to a given number + * of nanoseconds. Nukes the value set via {@link #withMillis(int)}. + * + * <p>For example, + * {@code new TimestampWithTimeZoneString(1970, 1, 1, 2, 3, 4, "GMT").withNanos(56789)} + * yields {@code TIMESTAMP WITH LOCAL TIME ZONE '1970-01-01 02:03:04.000056789 GMT'}. */ + public TimestampWithTimeZoneString withNanos(int nanos) { + Preconditions.checkArgument(nanos >= 0 && nanos < 1000000000); + return withFraction(DateTimeStringUtils.pad(9, nanos)); + } + + /** Sets the fraction field of a {@code TimestampString}. + * The precision is determined by the number of leading zeros. + * Trailing zeros are stripped. + * + * <p>For example, {@code + * new TimestampWithTimeZoneString(1970, 1, 1, 2, 3, 4, "GMT").withFraction("00506000")} + * yields {@code TIMESTAMP WITH LOCAL TIME ZONE '1970-01-01 02:03:04.00506 GMT'}. */ + public TimestampWithTimeZoneString withFraction(String fraction) { + return new TimestampWithTimeZoneString( + localDateTime.withFraction(fraction), timeZone); + } + + public TimestampWithTimeZoneString withTimeZone(TimeZone timeZone) { + if (this.timeZone.equals(timeZone)) { + return this; + } + String localDateTimeString = localDateTime.toString(); + String v; + String fraction; + int i = localDateTimeString.indexOf('.'); + if (i >= 0) { + v = localDateTimeString.substring(0, i); + fraction = localDateTimeString.substring(i + 1); + } else { + v = localDateTimeString; + fraction = null; + } + final DateTimeUtils.PrecisionTime pt = + DateTimeUtils.parsePrecisionDateTimeLiteral(v, + new SimpleDateFormat(DateTimeUtils.TIMESTAMP_FORMAT_STRING, Locale.ROOT), + this.timeZone, -1); + pt.getCalendar().setTimeZone(timeZone); + if (fraction != null) { + return new TimestampWithTimeZoneString( + pt.getCalendar().get(Calendar.YEAR), + pt.getCalendar().get(Calendar.MONTH) + 1, + pt.getCalendar().get(Calendar.DAY_OF_MONTH), + pt.getCalendar().get(Calendar.HOUR_OF_DAY), + pt.getCalendar().get(Calendar.MINUTE), + pt.getCalendar().get(Calendar.SECOND), + timeZone.getID()) + .withFraction(fraction); + } + return new TimestampWithTimeZoneString( + pt.getCalendar().get(Calendar.YEAR), + pt.getCalendar().get(Calendar.MONTH) + 1, + pt.getCalendar().get(Calendar.DAY_OF_MONTH), + pt.getCalendar().get(Calendar.HOUR_OF_DAY), + pt.getCalendar().get(Calendar.MINUTE), + pt.getCalendar().get(Calendar.SECOND), + timeZone.getID()); + } + + @Override public String toString() { + return v; + } + + @Override public boolean equals(Object o) { + // The value is in canonical form (no trailing zeros). + return o == this + || o instanceof TimestampWithTimeZoneString + && ((TimestampWithTimeZoneString) o).v.equals(v); + } + + @Override public int hashCode() { + return v.hashCode(); + } + + @Override public int compareTo(TimestampWithTimeZoneString o) { + return v.compareTo(o.v); + } + + public TimestampWithTimeZoneString round(int precision) { + Preconditions.checkArgument(precision >= 0); + return new TimestampWithTimeZoneString( + localDateTime.round(precision), timeZone); + } + + /** Creates a TimestampWithTimeZoneString that is a given number of milliseconds since + * the epoch UTC. */ + public static TimestampWithTimeZoneString fromMillisSinceEpoch(long millis) { + return new TimestampWithTimeZoneString( + DateTimeUtils.unixTimestampToString(millis) + " " + DateTimeUtils.UTC_ZONE.getID()) + .withMillis((int) DateTimeUtils.floorMod(millis, 1000)); + } + + /** Converts this TimestampWithTimeZoneString to a string, truncated or padded with + * zeroes to a given precision. */ + public String toString(int precision) { + Preconditions.checkArgument(precision >= 0); + return localDateTime.toString(precision) + " " + timeZone.getID(); + } + + public DateString getLocalDateString() { + return new DateString(localDateTime.toString().substring(0, 10)); + } + + public TimeString getLocalTimeString() { + return new TimeString(localDateTime.toString().substring(11)); + } + + public TimestampString getLocalTimestampString() { + return localDateTime; + } + +} + +// End TimestampWithTimeZoneString.java http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java index 9dd3019..1e6f1a3 100644 --- a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java +++ b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java @@ -274,7 +274,7 @@ public class CalciteRemoteDriverTest { @Test public void testRemoteTypeInfo() throws Exception { CalciteAssert.hr().with(REMOTE_CONNECTION_FACTORY) .metaData(GET_TYPEINFO) - .returns(CalciteAssert.checkResultCount(is(43))); + .returns(CalciteAssert.checkResultCount(is(45))); } @Test public void testRemoteTableTypes() throws Exception { http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java index 0fd99f8..f9a61fc 100644 --- a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java +++ b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java @@ -24,11 +24,13 @@ import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.util.DateString; import org.apache.calcite.util.TimeString; import org.apache.calcite.util.TimestampString; +import org.apache.calcite.util.TimestampWithTimeZoneString; import org.apache.calcite.util.Util; import org.junit.Test; import java.util.Calendar; +import java.util.TimeZone; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.core.Is.is; @@ -176,6 +178,77 @@ public class RexBuilderTest { assertThat(literal.getValueAs(TimestampString.class), notNullValue()); } + /** Tests + * {@link RexBuilder#makeTimestampWithLocalTimeZoneLiteral(TimestampWithTimeZoneString, int)}. */ + @Test public void testTimestampWithLocalTimeZoneLiteral() { + final RelDataTypeFactory typeFactory = + new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT); + final RelDataType timestampType = + typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE); + final RelDataType timestampType3 = + typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 3); + final RelDataType timestampType9 = + typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 9); + final RelDataType timestampType18 = + typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 18); + final RexBuilder builder = new RexBuilder(typeFactory); + + // The new way + final TimestampWithTimeZoneString ts = new TimestampWithTimeZoneString( + 1969, 7, 21, 2, 56, 15, TimeZone.getTimeZone("PST").getID()); + checkTimestampWithLocalTimeZone( + builder.makeLiteral(ts.getLocalTimestampString(), timestampType, false)); + + // Now with milliseconds + final TimestampWithTimeZoneString ts2 = ts.withMillis(56); + assertThat(ts2.toString(), is("1969-07-21 02:56:15.056 PST")); + final RexNode literal2 = builder.makeLiteral( + ts2.getLocalTimestampString(), timestampType3, false); + assertThat(((RexLiteral) literal2).getValue().toString(), is("1969-07-21 02:56:15.056")); + + // Now with nanoseconds + final TimestampWithTimeZoneString ts3 = ts.withNanos(56); + final RexNode literal3 = builder.makeLiteral( + ts3.getLocalTimestampString(), timestampType9, false); + assertThat(((RexLiteral) literal3).getValueAs(TimestampString.class) + .toString(), is("1969-07-21 02:56:15")); + final TimestampWithTimeZoneString ts3b = ts.withNanos(2345678); + final RexNode literal3b = builder.makeLiteral( + ts3b.getLocalTimestampString(), timestampType9, false); + assertThat(((RexLiteral) literal3b).getValueAs(TimestampString.class) + .toString(), is("1969-07-21 02:56:15.002")); + + // Now with a very long fraction + final TimestampWithTimeZoneString ts4 = ts.withFraction("102030405060708090102"); + final RexNode literal4 = builder.makeLiteral( + ts4.getLocalTimestampString(), timestampType18, false); + assertThat(((RexLiteral) literal4).getValueAs(TimestampString.class) + .toString(), is("1969-07-21 02:56:15.102")); + + // toString + assertThat(ts2.round(1).toString(), is("1969-07-21 02:56:15 PST")); + assertThat(ts2.round(2).toString(), is("1969-07-21 02:56:15.05 PST")); + assertThat(ts2.round(3).toString(), is("1969-07-21 02:56:15.056 PST")); + assertThat(ts2.round(4).toString(), is("1969-07-21 02:56:15.056 PST")); + + assertThat(ts2.toString(6), is("1969-07-21 02:56:15.056000 PST")); + assertThat(ts2.toString(1), is("1969-07-21 02:56:15.0 PST")); + assertThat(ts2.toString(0), is("1969-07-21 02:56:15 PST")); + + assertThat(ts2.round(0).toString(), is("1969-07-21 02:56:15 PST")); + assertThat(ts2.round(0).toString(0), is("1969-07-21 02:56:15 PST")); + assertThat(ts2.round(0).toString(1), is("1969-07-21 02:56:15.0 PST")); + assertThat(ts2.round(0).toString(2), is("1969-07-21 02:56:15.00 PST")); + } + + private void checkTimestampWithLocalTimeZone(RexNode node) { + assertThat(node.toString(), is("1969-07-21 02:56:15")); + RexLiteral literal = (RexLiteral) node; + assertThat(literal.getValue() instanceof TimestampString, is(true)); + assertThat(literal.getValue2() instanceof Long, is(true)); + assertThat(literal.getValue3() instanceof Long, is(true)); + } + /** Tests {@link RexBuilder#makeTimeLiteral(TimeString, int)}. */ @Test public void testTimeLiteral() { final RelDataTypeFactory typeFactory = http://git-wip-us.apache.org/repos/asf/calcite/blob/68abba04/core/src/test/java/org/apache/calcite/test/CalciteAssert.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java index 6db9435..3e6b414 100644 --- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java +++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java @@ -548,6 +548,9 @@ public class CalciteAssert { calciteConnection.getProperties().setProperty( CalciteConnectionProperty.CREATE_MATERIALIZATIONS.camelName(), Boolean.toString(materializationsEnabled)); + calciteConnection.getProperties().setProperty( + CalciteConnectionProperty.TIME_ZONE.camelName(), + DateTimeUtils.UTC_ZONE.getID()); } for (Pair<Hook, Function> hook : hooks) { closer.add(hook.left.addThread(hook.right));
