You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2022/06/12 20:34:57 UTC
[calcite] branch main updated: [CALCITE-5143] Allow custom time unit abbreviations in FLOOR, CEIL, EXTRACT, DATE_PART, DATEADD, DATEDIFF and similar functions
This is an automated email from the ASF dual-hosted git repository.
jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 75d064f65 [CALCITE-5143] Allow custom time unit abbreviations in FLOOR, CEIL, EXTRACT, DATE_PART, DATEADD, DATEDIFF and similar functions
75d064f65 is described below
commit 75d064f659a038371d84f9fdba7847e1631d1f1d
Author: xiejiajun <ji...@foxmail.com>
AuthorDate: Thu May 12 09:13:58 2022 +0800
[CALCITE-5143] Allow custom time unit abbreviations in FLOOR, CEIL, EXTRACT, DATE_PART, DATEADD, DATEDIFF and similar functions
Time unit abbreviations are specified in
SqlParser.Config.timeUnitCodes().
Since [CALCITE-2496], EXTRACT supports MICROSECOND and
NANOSECOND by default but FLOOR and CEIL do not. With this
change, you could configure FLOOR and CEIL to also support
those units.
Close apache/calcite#2807
---
.../org/apache/calcite/test/BabelParserTest.java | 30 ++++++
core/src/main/codegen/templates/Parser.jj | 102 +++++++++++++++------
.../calcite/sql/parser/SqlAbstractParserImpl.java | 7 ++
.../org/apache/calcite/sql/parser/SqlParser.java | 28 ++++++
.../apache/calcite/sql/parser/SqlParserTest.java | 94 ++++++++++++++++++-
.../org/apache/calcite/test/SqlOperatorTest.java | 10 +-
6 files changed, 235 insertions(+), 36 deletions(-)
diff --git a/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java b/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
index 777e34aa0..3fafb4899 100644
--- a/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
+++ b/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.calcite.test;
+import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.sql.dialect.MysqlSqlDialect;
import org.apache.calcite.sql.parser.SqlAbstractParserImpl;
@@ -34,6 +35,7 @@ import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
import static org.hamcrest.CoreMatchers.is;
@@ -184,6 +186,34 @@ class BabelParserTest extends SqlParserTest {
sql(sql).ok(expected);
}
+ /** Overrides, adding tests for DATEADD, DATEDIFF, DATE_PART functions
+ * in addition to EXTRACT. */
+ @Override protected void checkTimeUnitCodes(
+ Map<String, TimeUnit> timeUnitCodes) {
+ super.checkTimeUnitCodes(timeUnitCodes);
+
+ SqlParserFixture f = fixture()
+ .withConfig(config -> config.withTimeUnitCodes(timeUnitCodes));
+
+ timeUnitCodes.forEach((abbrev, timeUnit) -> {
+ String sql = "SELECT "
+ + "DATEADD(" + abbrev + ", 1, '2022-06-03 15:30:00.000'),"
+ + "DATEDIFF(" + abbrev + ", '2021-06-03 12:00:00.000', '2022-06-03 15:30:00.000'),"
+ + "DATE_PART(" + abbrev + ", '2022-06-03 15:30:00.000')";
+ String expected = "SELECT "
+ + "`DATEADD`(" + timeUnit + ", 1, '2022-06-03 15:30:00.000'), "
+ + "`DATEDIFF`(" + timeUnit + ", '2021-06-03 12:00:00.000', '2022-06-03 15:30:00.000'), "
+ + "`DATE_PART`(" + timeUnit + ", '2022-06-03 15:30:00.000')";
+ f.sql(sql).ok(expected);
+ });
+ f.sql("SELECT DATEADD(^A^, 1, NOW())")
+ .fails("'A' is not a valid datetime format");
+ if (timeUnitCodes.containsKey("S")) {
+ f.sql("SELECT DATEADD(S^.^A, 1, NOW())")
+ .fails("(?s).*Encountered \".\" at .*");
+ }
+ }
+
/** PostgreSQL and Redshift allow TIMESTAMP literals that contain only a
* date part. */
@Test void testShortTimestampLiteral() {
diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj
index 6b26d1066..d216a6af9 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -127,6 +127,7 @@ import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteTrace;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import java.io.Reader;
@@ -137,6 +138,7 @@ import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import static org.apache.calcite.util.Static.RESOURCE;
@@ -161,6 +163,7 @@ public class ${parser.class} extends SqlAbstractParserImpl
private Casing unquotedCasing;
private Casing quotedCasing;
private int identifierMaxLength;
+ private ImmutableMap<String, TimeUnit> timeUnitCodes;
private SqlConformance conformance;
/**
@@ -222,6 +225,10 @@ public class ${parser.class} extends SqlAbstractParserImpl
this.identifierMaxLength = identifierMaxLength;
}
+ public void setTimeUnitCodes(Map<String, TimeUnit> timeUnitCodes) {
+ this.timeUnitCodes = ImmutableMap.copyOf(timeUnitCodes);
+ }
+
public void setConformance(SqlConformance conformance) {
this.conformance = conformance;
}
@@ -5037,29 +5044,72 @@ SqlIntervalQualifier IntervalQualifierStart() :
}
/**
- * Parses time unit for EXTRACT, CEIL and FLOOR functions.
- * Note that it does't include NANOSECOND and MICROSECOND.
+ * Parses time unit for CEIL and FLOOR functions.
*/
TimeUnit TimeUnit() :
-{}
{
- <MILLISECOND> { return TimeUnit.MILLISECOND; }
-| <SECOND> { return TimeUnit.SECOND; }
-| <MINUTE> { return TimeUnit.MINUTE; }
-| <HOUR> { return TimeUnit.HOUR; }
-| <DAY> { return TimeUnit.DAY; }
-| <DOW> { return TimeUnit.DOW; }
-| <DOY> { return TimeUnit.DOY; }
-| <ISODOW> { return TimeUnit.ISODOW; }
-| <ISOYEAR> { return TimeUnit.ISOYEAR; }
-| <WEEK> { return TimeUnit.WEEK; }
-| <MONTH> { return TimeUnit.MONTH; }
-| <QUARTER> { return TimeUnit.QUARTER; }
-| <YEAR> { return TimeUnit.YEAR; }
-| <EPOCH> { return TimeUnit.EPOCH; }
-| <DECADE> { return TimeUnit.DECADE; }
-| <CENTURY> { return TimeUnit.CENTURY; }
-| <MILLENNIUM> { return TimeUnit.MILLENNIUM; }
+ final TimeUnit unit;
+}
+{
+ LOOKAHEAD(1)
+ (
+ <MILLISECOND> { return TimeUnit.MILLISECOND; }
+ | <SECOND> { return TimeUnit.SECOND; }
+ | <MINUTE> { return TimeUnit.MINUTE; }
+ | <HOUR> { return TimeUnit.HOUR; }
+ | <DAY> { return TimeUnit.DAY; }
+ | <DOW> { return TimeUnit.DOW; }
+ | <DOY> { return TimeUnit.DOY; }
+ | <ISODOW> { return TimeUnit.ISODOW; }
+ | <ISOYEAR> { return TimeUnit.ISOYEAR; }
+ | <WEEK> { return TimeUnit.WEEK; }
+ | <MONTH> { return TimeUnit.MONTH; }
+ | <QUARTER> { return TimeUnit.QUARTER; }
+ | <YEAR> { return TimeUnit.YEAR; }
+ | <EPOCH> { return TimeUnit.EPOCH; }
+ | <DECADE> { return TimeUnit.DECADE; }
+ | <CENTURY> { return TimeUnit.CENTURY; }
+ | <MILLENNIUM> { return TimeUnit.MILLENNIUM; }
+ )
+|
+ unit = TimeUnitIdentifier() { return unit; }
+}
+
+/**
+ * Parses time unit for the EXTRACT function.
+ * As for FLOOR and CEIL, but also includes NANOSECOND and MICROSECOND.
+ */
+TimeUnit TimeUnitForExtract() :
+{
+ final TimeUnit unit;
+}
+{
+ LOOKAHEAD(1)
+ (
+ <NANOSECOND> { return TimeUnit.NANOSECOND; }
+ | <MICROSECOND> { return TimeUnit.MICROSECOND; }
+ )
+|
+ unit = TimeUnit() { return unit; }
+}
+
+/**
+ * Parses a simple identifier as a TimeUnit.
+ */
+TimeUnit TimeUnitIdentifier() :
+{
+ final List<String> names = new ArrayList<String>();
+ final List<SqlParserPos> positions = new ArrayList<SqlParserPos>();
+}
+{
+ IdentifierSegment(names, positions) {
+ TimeUnit unit = timeUnitCodes.get(names.get(0));
+ if (unit != null) {
+ return unit;
+ }
+ throw SqlUtil.newContextException(positions.get(0),
+ RESOURCE.invalidDatetimeFormat(SqlIdentifier.getString(names)));
+ }
}
TimeUnit TimestampInterval() :
@@ -5930,16 +5980,10 @@ SqlNode BuiltinFunctionCall() :
return SqlStdOperatorTable.CAST.createCall(s.end(this), args);
}
|
- <EXTRACT> {
- s = span();
+ <EXTRACT> { s = span(); }
+ <LPAREN> unit = TimeUnitForExtract() {
+ args = startList(new SqlIntervalQualifier(unit, null, getPos()));
}
- <LPAREN>
- (
- <NANOSECOND> { unit = TimeUnit.NANOSECOND; }
- | <MICROSECOND> { unit = TimeUnit.MICROSECOND; }
- | unit = TimeUnit()
- )
- { args = startList(new SqlIntervalQualifier(unit, null, getPos())); }
<FROM>
e = Expression(ExprContext.ACCEPT_SUB_QUERY) { args.add(e); }
<RPAREN> {
diff --git a/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java b/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java
index 01e9d576a..0fa5602bf 100644
--- a/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java
@@ -17,6 +17,7 @@
package org.apache.calcite.sql.parser;
import org.apache.calcite.avatica.util.Casing;
+import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.config.CharLiteralStyle;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.calcite.runtime.CalciteException;
@@ -49,6 +50,7 @@ import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
@@ -542,6 +544,11 @@ public abstract class SqlAbstractParserImpl {
*/
public abstract void setIdentifierMaxLength(int identifierMaxLength);
+ /**
+ * Sets the map from identifier to time unit.
+ */
+ public abstract void setTimeUnitCodes(Map<String, TimeUnit> timeUnitCodes);
+
/**
* Sets the SQL language conformance level.
*/
diff --git a/core/src/main/java/org/apache/calcite/sql/parser/SqlParser.java b/core/src/main/java/org/apache/calcite/sql/parser/SqlParser.java
index 352f1eadc..a0c514774 100644
--- a/core/src/main/java/org/apache/calcite/sql/parser/SqlParser.java
+++ b/core/src/main/java/org/apache/calcite/sql/parser/SqlParser.java
@@ -18,6 +18,7 @@ package org.apache.calcite.sql.parser;
import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.avatica.util.Quoting;
+import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.config.CharLiteralStyle;
import org.apache.calcite.config.Lex;
import org.apache.calcite.runtime.CalciteContextException;
@@ -29,6 +30,7 @@ import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlDelegatingConformance;
import org.apache.calcite.util.SourceStringReader;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.immutables.value.Value;
@@ -36,6 +38,7 @@ import org.immutables.value.Value;
import java.io.Reader;
import java.io.StringReader;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -45,6 +48,13 @@ import java.util.Set;
@SuppressWarnings("deprecation")
public class SqlParser {
public static final int DEFAULT_IDENTIFIER_MAX_LENGTH = 128;
+
+ /** Default value of {@link Config#timeUnitCodes()}.
+ * The map is empty, which means that there are no abbreviations other than
+ * the time unit names ("YEAR", "SECOND", etc.) */
+ public static final ImmutableMap<String, TimeUnit> DEFAULT_IDENTIFIER_TIMEUNIT_MAP =
+ ImmutableMap.of();
+
@Deprecated // to be removed before 2.0
public static final boolean DEFAULT_ALLOW_BANG_EQUAL =
SqlConformanceEnum.DEFAULT.isBangEqualAllowed();
@@ -60,6 +70,7 @@ public class SqlParser {
parser.setQuotedCasing(config.quotedCasing());
parser.setUnquotedCasing(config.unquotedCasing());
parser.setIdentifierMaxLength(config.identifierMaxLength());
+ parser.setTimeUnitCodes(config.timeUnitCodes());
parser.setConformance(config.conformance());
parser.switchTo(SqlAbstractParserImpl.LexicalState.forConfig(config));
}
@@ -309,6 +320,18 @@ public class SqlParser {
/** Sets {@link #charLiteralStyles()}. */
Config withCharLiteralStyles(Iterable<CharLiteralStyle> charLiteralStyles);
+ /** Returns a mapping from abbreviations to time units.
+ *
+ * <p>For example, if the map contains the entry
+ * ("Y", {@link TimeUnit#YEAR}) then you can write
+ * "{@code EXTRACT(S FROM orderDate)}". */
+ @Value.Default default Map<String, TimeUnit> timeUnitCodes() {
+ return DEFAULT_IDENTIFIER_TIMEUNIT_MAP;
+ }
+
+ /** Sets {@link #timeUnitCodes()}. */
+ Config withTimeUnitCodes(Map<String, ? extends TimeUnit> timeUnitCodes);
+
@Value.Default default SqlParserImplFactory parserFactory() {
return SqlParserImpl.FACTORY;
}
@@ -358,6 +381,11 @@ public class SqlParser {
return setConfig(config.withIdentifierMaxLength(identifierMaxLength));
}
+ public ConfigBuilder setIdentifierTimeUnitMap(
+ ImmutableMap<String, TimeUnit> identifierTimeUnitMap) {
+ return setConfig(config.withTimeUnitCodes(identifierTimeUnitMap));
+ }
+
@SuppressWarnings("unused")
@Deprecated // to be removed before 2.0
public ConfigBuilder setAllowBangEqual(final boolean allowBangEqual) {
diff --git a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 7416d2758..31ae30ba7 100644
--- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -17,6 +17,7 @@
package org.apache.calcite.sql.parser;
import org.apache.calcite.avatica.util.Quoting;
+import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.sql.SqlExplain;
@@ -30,6 +31,7 @@ import org.apache.calcite.sql.SqlSetOption;
import org.apache.calcite.sql.SqlWriterConfig;
import org.apache.calcite.sql.dialect.AnsiSqlDialect;
import org.apache.calcite.sql.dialect.SparkSqlDialect;
+import org.apache.calcite.sql.parser.SqlParser.Config;
import org.apache.calcite.sql.pretty.SqlPrettyWriter;
import org.apache.calcite.sql.test.SqlTestFactory;
import org.apache.calcite.sql.test.SqlTests;
@@ -65,6 +67,7 @@ import java.util.Map;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
@@ -7676,6 +7679,93 @@ public class SqlParserTest {
.fails("(?s)Encountered \"to\".*");
}
+ /** Tests that EXTRACT, FLOOR, CEIL functions accept abbreviations for
+ * time units (such as "Y" for "YEAR") when configured via
+ * {@link Config#timeUnitCodes()}. */
+ @Test void testTimeUnitCodes() {
+ final Map<String, TimeUnit> simpleCodes =
+ ImmutableMap.<String, TimeUnit>builder()
+ .put("Y", TimeUnit.YEAR)
+ .put("M", TimeUnit.MONTH)
+ .put("D", TimeUnit.DAY)
+ .put("H", TimeUnit.HOUR)
+ .put("N", TimeUnit.MINUTE)
+ .put("S", TimeUnit.SECOND)
+ .build();
+
+ // Time unit abbreviations for Microsoft SQL Server
+ final Map<String, TimeUnit> mssqlCodes =
+ ImmutableMap.<String, TimeUnit>builder()
+ .put("Y", TimeUnit.YEAR)
+ .put("YY", TimeUnit.YEAR)
+ .put("YYYY", TimeUnit.YEAR)
+ .put("Q", TimeUnit.QUARTER)
+ .put("QQ", TimeUnit.QUARTER)
+ .put("M", TimeUnit.MONTH)
+ .put("MM", TimeUnit.MONTH)
+ .put("W", TimeUnit.WEEK)
+ .put("WK", TimeUnit.WEEK)
+ .put("WW", TimeUnit.WEEK)
+ .put("DY", TimeUnit.DOY)
+ .put("DW", TimeUnit.DOW)
+ .put("D", TimeUnit.DAY)
+ .put("DD", TimeUnit.DAY)
+ .put("H", TimeUnit.HOUR)
+ .put("HH", TimeUnit.HOUR)
+ .put("N", TimeUnit.MINUTE)
+ .put("MI", TimeUnit.MINUTE)
+ .put("S", TimeUnit.SECOND)
+ .put("SS", TimeUnit.SECOND)
+ .put("MS", TimeUnit.MILLISECOND)
+ .build();
+
+ checkTimeUnitCodes(Config.DEFAULT.timeUnitCodes());
+ checkTimeUnitCodes(simpleCodes);
+ checkTimeUnitCodes(mssqlCodes);
+ }
+
+ /** Checks parsing of built-in functions that accept time unit
+ * abbreviations.
+ *
+ * <p>For example, {@code EXTRACT(Y FROM orderDate)} is using
+ * "Y" as an abbreviation for "YEAR".
+ *
+ * <p>Override if your parser supports more such functions. */
+ protected void checkTimeUnitCodes(Map<String, TimeUnit> timeUnitCodes) {
+ SqlParserFixture f = fixture()
+ .withConfig(config -> config.withTimeUnitCodes(timeUnitCodes));
+ BiConsumer<String, TimeUnit> validConsumer = (abbrev, timeUnit) -> {
+ f.sql("select extract(" + abbrev + " from x)")
+ .ok("SELECT EXTRACT(" + timeUnit + " FROM `X`)");
+ f.sql("select floor(x to " + abbrev + ")")
+ .ok("SELECT FLOOR(`X` TO " + timeUnit + ")");
+ f.sql("select ceil(x to " + abbrev + ")")
+ .ok("SELECT CEIL(`X` TO " + timeUnit + ")");
+ };
+ BiConsumer<String, TimeUnit> invalidConsumer = (abbrev, timeUnit) -> {
+ final String upAbbrev = abbrev.toUpperCase(Locale.ROOT);
+ f.sql("select extract(^" + abbrev + "^ from x)")
+ .fails("'" + upAbbrev + "' is not a valid datetime format");
+ f.sql("SELECT FLOOR(x to ^" + abbrev + "^)")
+ .fails("'" + upAbbrev + "' is not a valid datetime format");
+ f.sql("SELECT CEIL(x to ^" + abbrev + "^)")
+ .fails("'" + upAbbrev + "' is not a valid datetime format");
+ };
+
+ // Check that each valid code passes each query that it should.
+ timeUnitCodes.forEach(validConsumer);
+
+ // If "M" is a valid code then "m" should be also.
+ timeUnitCodes.forEach((abbrev, timeUnit) ->
+ validConsumer.accept(abbrev.toLowerCase(Locale.ROOT), timeUnit));
+
+ // Check that invalid codes generate the right error messages.
+ final Map<String, TimeUnit> invalidCodes =
+ ImmutableMap.of("A", TimeUnit.YEAR,
+ "a", TimeUnit.YEAR);
+ invalidCodes.forEach(invalidConsumer);
+ }
+
@Test void testGeometry() {
expr("cast(null as ^geometry^)")
.fails("Geo-spatial extensions and the GEOMETRY data type are not enabled");
@@ -10085,7 +10175,7 @@ public class SqlParserTest {
.withClauseEndsLine(random.nextBoolean());
}
- private String toSqlString(SqlNodeList sqlNodeList,
+ static String toSqlString(SqlNodeList sqlNodeList,
UnaryOperator<SqlWriterConfig> transform) {
return sqlNodeList.stream()
.map(node -> node.toSqlString(transform).getSql())
@@ -10101,7 +10191,7 @@ public class SqlParserTest {
return constants[random.nextInt(constants.length)];
}
- private void checkList(SqlNodeList sqlNodeList,
+ static void checkList(SqlNodeList sqlNodeList,
UnaryOperator<String> converter, List<String> expected) {
assertThat(sqlNodeList.size(), is(expected.size()));
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 26919dfb9..a277b1ea6 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -2641,7 +2641,7 @@ public class SqlOperatorTest {
checkNullOperand(f1, "<>");
}
- private void checkNullOperand(SqlOperatorFixture f, String op) {
+ private static void checkNullOperand(SqlOperatorFixture f, String op) {
f.checkBoolean("1 " + op + " null", null);
f.checkBoolean("null " + op + " -3", null);
f.checkBoolean("null " + op + " null", null);
@@ -7254,9 +7254,9 @@ public class SqlOperatorTest {
f.checkFails("^floor('abcde' to minute)^",
"(?s)Cannot apply 'FLOOR' to arguments .*", false);
f.checkFails("floor(timestamp '2015-02-19 12:34:56.78' to ^microsecond^)",
- "(?s)Encountered \"microsecond\" at .*", false);
+ "'MICROSECOND' is not a valid datetime format", false);
f.checkFails("floor(timestamp '2015-02-19 12:34:56.78' to ^nanosecond^)",
- "(?s)Encountered \"nanosecond\" at .*", false);
+ "'NANOSECOND' is not a valid datetime format", false);
f.checkScalar("floor(time '12:34:56' to minute)",
"12:34:00", "TIME(0) NOT NULL");
f.checkScalar("floor(timestamp '2015-02-19 12:34:56.78' to second)",
@@ -7295,9 +7295,9 @@ public class SqlOperatorTest {
f.checkFails("^ceil('abcde' to minute)^",
"(?s)Cannot apply 'CEIL' to arguments .*", false);
f.checkFails("ceil(timestamp '2015-02-19 12:34:56.78' to ^microsecond^)",
- "(?s)Encountered \"microsecond\" at .*", false);
+ "'MICROSECOND' is not a valid datetime format", false);
f.checkFails("ceil(timestamp '2015-02-19 12:34:56.78' to ^nanosecond^)",
- "(?s)Encountered \"nanosecond\" at .*", false);
+ "'NANOSECOND' is not a valid datetime format", false);
f.checkScalar("ceil(time '12:34:56' to minute)",
"12:35:00", "TIME(0) NOT NULL");
f.checkScalar("ceil(time '12:59:56' to minute)",