Skip to content

Commit

Permalink
[CALCITE-5450] Allow 'WEEK(weekday)' time frame as argument to functi…
Browse files Browse the repository at this point in the history
…ons such as EXTRACT, DATE_TRUNC

In OperandTypes, add strategies for matching DATE, TIME,
TIMESTAMP frames; DATE and TIMESTAMP frames include
ISOYEAR, WEEK(THURSDAY).

Enable tests for ISOYEAR, now that the upgrade to Avatica
1.23 has brought in the fix to [CALCITE-5369], ISOYEAR in
FLOOR and CEIL functions.
  • Loading branch information
tanclary authored and julianhyde committed Jan 25, 2023
1 parent 016dcd4 commit d9eeba4
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 89 deletions.
30 changes: 21 additions & 9 deletions babel/src/test/resources/sql/big-query.iq
Original file line number Diff line number Diff line change
Expand Up @@ -1827,22 +1827,34 @@ SELECT TIMESTAMP_DIFF("2001-02-01 01:00:00", "2001-02-01 00:00:01", HOUR) AS neg
# Returns DATE
WITH Dates AS (
SELECT DATE_TRUNC(DATE '2008-12-25', YEAR) as d , "year" as frame UNION ALL
SELECT DATE_TRUNC(DATE '2008-12-25', ISOYEAR), "isoyear" UNION ALL
SELECT DATE_TRUNC(DATE '2008-12-25', QUARTER), "quarter" UNION ALL
SELECT DATE_TRUNC(DATE '2008-12-25', MONTH), "month" UNION ALL
SELECT DATE_TRUNC(DATE '2008-12-25', WEEK), "week" UNION ALL
SELECT DATE_TRUNC(DATE '2008-12-25', WEEK(SUNDAY)), "week(sunday)" UNION ALL
SELECT DATE_TRUNC(DATE '2008-12-25', WEEK(MONDAY)), "week(monday)" UNION ALL
SELECT DATE_TRUNC(DATE '2008-12-25', WEEK(TUESDAY)), "week(tuesday)" UNION ALL
SELECT DATE_TRUNC(DATE '2008-12-25', ISOWEEK), "isoweek" UNION ALL
SELECT DATE_TRUNC(DATE '2008-12-25', DAY), "day"
)
SELECT
*
FROM Dates;
+------------+---------+
| d | frame |
+------------+---------+
| 2008-01-01 | year |
| 2008-10-01 | quarter |
| 2008-12-01 | month |
| 2008-12-25 | day |
+------------+---------+
(4 rows)
+------------+---------------+
| d | frame |
+------------+---------------+
| 2008-01-01 | year |
| 2007-12-31 | isoyear |
| 2008-10-01 | quarter |
| 2008-12-01 | month |
| 2008-12-21 | week |
| 2008-12-21 | week(sunday) |
| 2008-12-22 | week(monday) |
| 2008-12-23 | week(tuesday) |
| 2008-12-22 | isoweek |
| 2008-12-25 | day |
+------------+---------------+
(10 rows)

!ok

Expand Down
95 changes: 59 additions & 36 deletions core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -5023,59 +5023,75 @@ SqlIntervalQualifier IntervalQualifierStart() :
}
}

/**
* Parses time unit for CEIL and FLOOR functions.
/** Parses a built-in time unit (e.g. "YEAR")
* or user-defined time frame (e.g. "MINUTE15")
* and in each case returns a {@link SqlIntervalQualifier}.
*
* <p>Includes NANOSECOND, MILLISECOND, which were previously only allowed in
* the EXTRACT function.
* <p>The units are used in several functions, incuding CEIL, FLOOR, EXTRACT.
* Includes NANOSECOND, MILLISECOND, which were previously allowed in EXTRACT
* but not CEIL, FLOOR.
*
* <p>Includes {@code WEEK} and {@code WEEK(SUNDAY)} through
{@code WEEK(SATURDAY)}.
*
* <p>Does not include SQL_TSI_DAY, SQL_TSI_FRAC_SECOND etc. These will be
* parsed as identifiers and can be resolved in the validator if they are
* registered as abbreviations in your time frame set.
*/
TimeUnit TimeUnit() :
{
}
{
<NANOSECOND> { return TimeUnit.NANOSECOND; }
| <MICROSECOND> { return TimeUnit.MICROSECOND; }
| <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; }
}

/** Parses a built-in time unit (e.g. "YEAR")
* or user-defined time frame (e.g. "MINUTE15")
* and in each case returns a {@link SqlIntervalQualifier}. */
SqlIntervalQualifier TimeUnitOrName() : {
final Span span;
final String w;
final TimeUnit unit;
final SqlIdentifier unitName;
}
{
LOOKAHEAD(1)
unit = TimeUnit() {
return new SqlIntervalQualifier(unit, null, getPos());
}
LOOKAHEAD(2)
<NANOSECOND> { return new SqlIntervalQualifier(TimeUnit.NANOSECOND, null, getPos()); }
| <MICROSECOND> { return new SqlIntervalQualifier(TimeUnit.MICROSECOND, null, getPos()); }
| <MILLISECOND> { return new SqlIntervalQualifier(TimeUnit.MILLISECOND, null, getPos()); }
| <SECOND> { return new SqlIntervalQualifier(TimeUnit.SECOND, null, getPos()); }
| <MINUTE> { return new SqlIntervalQualifier(TimeUnit.MINUTE, null, getPos()); }
| <HOUR> { return new SqlIntervalQualifier(TimeUnit.HOUR, null, getPos()); }
| <DAY> { return new SqlIntervalQualifier(TimeUnit.DAY, null, getPos()); }
| <DOW> { return new SqlIntervalQualifier(TimeUnit.DOW, null, getPos()); }
| <DOY> { return new SqlIntervalQualifier(TimeUnit.DOY, null, getPos()); }
| <ISODOW> { return new SqlIntervalQualifier(TimeUnit.ISODOW, null, getPos()); }
| <ISOYEAR> { return new SqlIntervalQualifier(TimeUnit.ISOYEAR, null, getPos()); }
| <WEEK> { span = span(); }
(
LOOKAHEAD(2)
<LPAREN> w = weekdayName() <RPAREN> {
return new SqlIntervalQualifier(w, span.end(this));
}
|
{ return new SqlIntervalQualifier(TimeUnit.WEEK, null, getPos()); }
)
| <MONTH> { return new SqlIntervalQualifier(TimeUnit.MONTH, null, getPos()); }
| <QUARTER> { return new SqlIntervalQualifier(TimeUnit.QUARTER, null, getPos()); }
| <YEAR> { return new SqlIntervalQualifier(TimeUnit.YEAR, null, getPos()); }
| <EPOCH> { return new SqlIntervalQualifier(TimeUnit.EPOCH, null, getPos()); }
| <DECADE> { return new SqlIntervalQualifier(TimeUnit.DECADE, null, getPos()); }
| <CENTURY> { return new SqlIntervalQualifier(TimeUnit.CENTURY, null, getPos()); }
| <MILLENNIUM> { return new SqlIntervalQualifier(TimeUnit.MILLENNIUM, null, getPos()); }
| unitName = SimpleIdentifier() {
return new SqlIntervalQualifier(unitName.getSimple(),
unitName.getParserPosition());
}
}

String weekdayName() :
{
}
{
<SUNDAY> { return "WEEK_SUNDAY"; }
| <MONDAY> { return "WEEK_MONDAY"; }
| <TUESDAY> { return "WEEK_TUESDAY"; }
| <WEDNESDAY> { return "WEEK_WEDNESDAY"; }
| <THURSDAY> { return "WEEK_THURSDAY"; }
| <FRIDAY> { return "WEEK_FRIDAY"; }
| <SATURDAY> { return "WEEK_SATURDAY"; }
}

/**
* Parses a dynamic parameter marker.
*/
Expand Down Expand Up @@ -7712,6 +7728,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < FRAC_SECOND: "FRAC_SECOND" >
| < FRAME_ROW: "FRAME_ROW" >
| < FREE: "FREE" >
| < FRIDAY: "FRIDAY" >
| < FROM: "FROM" > { beforeTableName(); }
| < FULL: "FULL" >
| < FUNCTION: "FUNCTION" >
Expand Down Expand Up @@ -7830,6 +7847,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < MOD: "MOD" >
| < MODIFIES: "MODIFIES" >
| < MODULE: "MODULE" >
| < MONDAY: "MONDAY" >
| < MONTH: "MONTH" >
| < MONTHS: "MONTHS" >
| < MORE_: "MORE" >
Expand Down Expand Up @@ -7977,6 +7995,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < ROW_NUMBER: "ROW_NUMBER" >
| < ROWS: "ROWS" >
| < RUNNING: "RUNNING" >
| < SATURDAY: "SATURDAY" >
| < SAVEPOINT: "SAVEPOINT" >
| < SCALAR: "SCALAR" >
| < SCALE: "SCALE" >
Expand Down Expand Up @@ -8090,6 +8109,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < SUBSTRING_REGEX: "SUBSTRING_REGEX" >
| < SUCCEEDS: "SUCCEEDS" >
| < SUM: "SUM" >
| < SUNDAY: "SUNDAY" >
| < SYMMETRIC: "SYMMETRIC" >
| < SYSTEM: "SYSTEM" >
| < SYSTEM_TIME: "SYSTEM_TIME" >
Expand All @@ -8099,6 +8119,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < TABLESAMPLE: "TABLESAMPLE" >
| < TEMPORARY: "TEMPORARY" >
| < THEN: "THEN" >
| < THURSDAY: "THURSDAY" >
| < TIES: "TIES" >
| < TIME: "TIME" >
| < TIME_DIFF: "TIME_DIFF" >
Expand Down Expand Up @@ -8132,6 +8153,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < TRIM_ARRAY: "TRIM_ARRAY" >
| < TRUE: "TRUE" >
| < TRUNCATE: "TRUNCATE" >
| < TUESDAY: "TUESDAY" >
| < TUMBLE: "TUMBLE" >
| < TYPE: "TYPE" >
| < UESCAPE: "UESCAPE" >
Expand Down Expand Up @@ -8169,6 +8191,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < VERSION: "VERSION" >
| < VERSIONING: "VERSIONING" >
| < VIEW: "VIEW" >
| < WEDNESDAY: "WEDNESDAY" >
| < WEEK: "WEEK" >
| < WEEKS: "WEEKS" >
| < WHEN: "WHEN" >
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2181,6 +2181,7 @@ private static class FloorImplementor extends MethodNameImplementor {
"timeUnitRange");
switch (timeUnitRange) {
case YEAR:
case ISOYEAR:
case QUARTER:
case MONTH:
case WEEK:
Expand Down
24 changes: 23 additions & 1 deletion core/src/main/java/org/apache/calcite/rel/type/TimeFrames.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
import org.apache.calcite.avatica.util.DateTimeUtils;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.avatica.util.TimeUnitRange;
import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.util.MonotonicSupplier;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.TimestampString;

import org.apache.commons.math3.fraction.BigFraction;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Iterables;
Expand All @@ -36,6 +38,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
Expand All @@ -51,15 +54,28 @@ public class TimeFrames {
private TimeFrames() {
}

/** The names of the frames that are WEEK starting on each week day.
* Entry 0 is "WEEK_SUNDAY" and entry 6 is "WEEK_SATURDAY". */
public static final List<String> WEEK_FRAME_NAMES =
ImmutableList.of("WEEK_SUNDAY",
"WEEK_MONDAY",
"WEEK_TUESDAY",
"WEEK_WEDNESDAY",
"WEEK_THURSDAY",
"WEEK_FRIDAY",
"WEEK_SATURDAY");

/** The core time frame set. Includes the time frames for all Avatica time
* units plus ISOWEEK:
* units plus ISOWEEK and week offset for each week day:
*
* <ul>
* <li>SECOND, and multiples MINUTE, HOUR, DAY, WEEK (starts on a Sunday),
* sub-multiples MILLISECOND, MICROSECOND, NANOSECOND,
* quotients DOY, DOW;
* <li>MONTH, and multiples QUARTER, YEAR, DECADE, CENTURY, MILLENNIUM;
* <li>ISOYEAR, and sub-unit ISOWEEK (starts on a Monday), quotient ISODOW;
* <li>WEEK(<i>weekday</i>) with <i>weekday</i> being one of
* SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY.
* </ul>
*
* <p>Does not include EPOCH.
Expand Down Expand Up @@ -94,12 +110,18 @@ private static BuilderImpl addCore(BuilderImpl b) {
b.addSub("ISOWEEK", false, 7, TimeUnit.DAY.name(),
new TimestampString(1970, 1, 5, 0, 0, 0)); // a monday

// Add "WEEK(SUNDAY)" through "WEEK(SATURDAY)"
Ord.forEach(WEEK_FRAME_NAMES, (frameName, i) ->
b.addSub(frameName, false, 7,
"DAY", new TimestampString(1970, 1, 4 + i, 0, 0, 0)));

b.addQuotient(TimeUnit.DOY, TimeUnit.DAY, TimeUnit.YEAR);
b.addQuotient(TimeUnit.DOW, TimeUnit.DAY, TimeUnit.WEEK);
b.addQuotient(TimeUnit.ISODOW.name(), TimeUnit.DAY.name(), "ISOWEEK");

b.addRollup(TimeUnit.DAY, TimeUnit.MONTH);
b.addRollup("ISOWEEK", TimeUnit.ISOYEAR.name());

return b;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package org.apache.calcite.sql.fun;

import org.apache.calcite.avatica.util.TimeUnitRange;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.SqlAggFunction;
Expand All @@ -43,13 +42,10 @@
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Optionality;

import com.google.common.collect.ImmutableSet;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static org.apache.calcite.sql.fun.SqlLibrary.BIG_QUERY;
import static org.apache.calcite.sql.fun.SqlLibrary.CALCITE;
Expand Down Expand Up @@ -672,31 +668,6 @@ private SqlLibraryOperators() {
OperandTypes.STRING_STRING,
SqlFunctionCategory.TIMEDATE);

private static final Set<TimeUnitRange> TIME_UNITS =
ImmutableSet.of(TimeUnitRange.HOUR,
TimeUnitRange.MINUTE,
TimeUnitRange.SECOND);

private static final Set<TimeUnitRange> MONTH_UNITS =
ImmutableSet.of(TimeUnitRange.MILLENNIUM,
TimeUnitRange.CENTURY,
TimeUnitRange.DECADE,
TimeUnitRange.YEAR,
TimeUnitRange.QUARTER,
TimeUnitRange.MONTH);

private static final Set<TimeUnitRange> DAY_UNITS =
ImmutableSet.of(TimeUnitRange.WEEK,
TimeUnitRange.DAY);

private static final Set<TimeUnitRange> DATE_UNITS =
ImmutableSet.<TimeUnitRange>builder()
.addAll(MONTH_UNITS).addAll(DAY_UNITS).build();

private static final Set<TimeUnitRange> TIMESTAMP_UNITS =
ImmutableSet.<TimeUnitRange>builder()
.addAll(DATE_UNITS).addAll(TIME_UNITS).build();

/** The "TIMESTAMP_ADD(timestamp, interval)" function (BigQuery), the
* two-argument variant of the built-in
* {@link SqlStdOperatorTable#TIMESTAMP_ADD TIMESTAMPADD} function, which has
Expand Down Expand Up @@ -745,7 +716,7 @@ private SqlLibraryOperators() {
SqlBasicFunction.create("DATE_TRUNC",
ReturnTypes.DATE_NULLABLE,
OperandTypes.sequence("'DATE_TRUNC(<DATE>, <DATETIME_INTERVAL>)'",
OperandTypes.DATE, OperandTypes.interval(DATE_UNITS)),
OperandTypes.DATE, OperandTypes.dateInterval()),
SqlFunctionCategory.TIMEDATE);

/** The "TIME_SUB(time, interval)" function (BigQuery);
Expand All @@ -767,7 +738,7 @@ private SqlLibraryOperators() {
SqlBasicFunction.create("TIME_TRUNC",
ReturnTypes.TIME_NULLABLE,
OperandTypes.sequence("'TIME_TRUNC(<TIME>, <DATETIME_INTERVAL>)'",
OperandTypes.TIME, OperandTypes.interval(TIME_UNITS)),
OperandTypes.TIME, OperandTypes.timeInterval()),
SqlFunctionCategory.TIMEDATE);

/** The "TIMESTAMP_SUB(timestamp, interval)" function (BigQuery);
Expand All @@ -790,7 +761,7 @@ private SqlLibraryOperators() {
ReturnTypes.TIMESTAMP_NULLABLE,
OperandTypes.sequence(
"'TIMESTAMP_TRUNC(<TIMESTAMP>, <DATETIME_INTERVAL>)'",
OperandTypes.TIMESTAMP, OperandTypes.interval(TIMESTAMP_UNITS)),
OperandTypes.TIMESTAMP, OperandTypes.timestampInterval()),
SqlFunctionCategory.TIMEDATE);

/** The "TIMESTAMP_SECONDS(bigint)" function; returns a TIMESTAMP value
Expand Down
Loading

0 comments on commit d9eeba4

Please sign in to comment.