Skip to content

Commit bdb4e1f

Browse files
olivrleejulianhyde
authored andcommitted
[CALCITE-5447] Add DATE_TRUNC function (enabled in BigQuery library)
In order to keep redshift.iq (the Quidem test for the Redshift dialect using the Babel parser) working, we ensure that the Redshift variant of DATE_TRUNC (which has arguments in a different order than the BigQuery variant) can still be handled by the parser, even though we still cannot execute it. Close apache#3009
1 parent f68f973 commit bdb4e1f

File tree

8 files changed

+101
-15
lines changed

8 files changed

+101
-15
lines changed

babel/src/test/resources/sql/big-query.iq

+18-8
Original file line numberDiff line numberDiff line change
@@ -1752,16 +1752,26 @@ SELECT TIMESTAMP_DIFF("2001-02-01 01:00:00", "2001-02-01 00:00:01", HOUR) AS neg
17521752
# corresponding Gregorian calendar year.
17531753
#
17541754
# Returns DATE
1755+
WITH Dates AS (
1756+
SELECT DATE_TRUNC(DATE '2008-12-25', YEAR) as d , "year" as frame UNION ALL
1757+
SELECT DATE_TRUNC(DATE '2008-12-25', QUARTER), "quarter" UNION ALL
1758+
SELECT DATE_TRUNC(DATE '2008-12-25', MONTH), "month" UNION ALL
1759+
SELECT DATE_TRUNC(DATE '2008-12-25', DAY), "day"
1760+
)
1761+
SELECT
1762+
*
1763+
FROM Dates;
1764+
+------------+---------+
1765+
| d | frame |
1766+
+------------+---------+
1767+
| 2008-01-01 | year |
1768+
| 2008-10-01 | quarter |
1769+
| 2008-12-01 | month |
1770+
| 2008-12-25 | day |
1771+
+------------+---------+
1772+
(4 rows)
17551773

1756-
!if (false) {
1757-
SELECT DATE_TRUNC(DATE '2008-12-25', MONTH) AS month;
1758-
+------------+
1759-
| month |
1760-
+------------+
1761-
| 2008-12-01 |
1762-
+------------+
17631774
!ok
1764-
!}
17651775

17661776
# In the following example, the original date falls on a
17671777
# Sunday. Because the date_part is WEEK(MONDAY), DATE_TRUNC returns

core/src/main/codegen/default_config.fmpp

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ parser: {
8383
"CURSOR_NAME"
8484
"DATA"
8585
"DATABASE"
86+
"DATE_TRUNC"
8687
"DATETIME_INTERVAL_CODE"
8788
"DATETIME_INTERVAL_PRECISION"
8889
"DAYS"

core/src/main/codegen/templates/Parser.jj

+36
Original file line numberDiff line numberDiff line change
@@ -6035,6 +6035,8 @@ SqlNode BuiltinFunctionCall() :
60356035
<RPAREN> {
60366036
return SqlStdOperatorTable.TRIM.createCall(s.end(this), args);
60376037
}
6038+
|
6039+
node = DateTruncFunctionCall() { return node; }
60386040
|
60396041
node = TimestampAddFunctionCall() { return node; }
60406042
|
@@ -6619,6 +6621,33 @@ SqlCall TimestampDiff3FunctionCall() :
66196621
}
66206622
}
66216623

6624+
/**
6625+
* Parses a call to DATE_TRUNC.
6626+
*/
6627+
SqlCall DateTruncFunctionCall() :
6628+
{
6629+
final List<SqlNode> args = new ArrayList<SqlNode>();
6630+
final Span s;
6631+
final SqlIntervalQualifier unit;
6632+
}
6633+
{
6634+
<DATE_TRUNC> { s = span(); }
6635+
<LPAREN>
6636+
AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
6637+
<COMMA>
6638+
// A choice of arguments allows us to support both
6639+
// the BigQuery variant, e.g. "DATE_TRUNC(d, YEAR)",
6640+
// and the Redshift variant, e.g. "DATE_TRUNC('year', DATE '2008-09-08')".
6641+
(
6642+
unit = TimeUnitOrName() { args.add(unit); }
6643+
|
6644+
AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
6645+
)
6646+
<RPAREN> {
6647+
return SqlLibraryOperators.DATE_TRUNC.createCall(s.end(this), args);
6648+
}
6649+
}
6650+
66226651
/**
66236652
* Parses a call to TIMESTAMP_TRUNC.
66246653
*/
@@ -7173,6 +7202,12 @@ SqlNode JdbcFunctionCall() :
71737202
name = call.getOperator().getName();
71747203
args = new SqlNodeList(call.getOperandList(), getPos());
71757204
}
7205+
|
7206+
LOOKAHEAD(1)
7207+
call = DateTruncFunctionCall() {
7208+
name = call.getOperator().getName();
7209+
args = new SqlNodeList(call.getOperandList(), getPos());
7210+
}
71767211
|
71777212
LOOKAHEAD(1)
71787213
call = TimestampTruncFunctionCall() {
@@ -7566,6 +7601,7 @@ SqlPostfixOperator PostfixRowOperator() :
75667601
| < DATA: "DATA" >
75677602
| < DATABASE: "DATABASE" >
75687603
| < DATE: "DATE" >
7604+
| < DATE_TRUNC: "DATE_TRUNC" >
75697605
| < DATETIME_INTERVAL_CODE: "DATETIME_INTERVAL_CODE" >
75707606
| < DATETIME_INTERVAL_PRECISION: "DATETIME_INTERVAL_PRECISION" >
75717607
| < DAY: "DAY" >

core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE;
126126
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATEADD;
127127
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE_FROM_UNIX_DATE;
128+
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE_TRUNC;
128129
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DAYNAME;
129130
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DIFFERENCE;
130131
import static org.apache.calcite.sql.fun.SqlLibraryOperators.ENDS_WITH;
@@ -514,10 +515,10 @@ Builder populate2() {
514515

515516
// TIMESTAMP_TRUNC and TIME_TRUNC methods are syntactic sugar for standard
516517
// datetime FLOOR.
518+
map.put(DATE_TRUNC, map.get(FLOOR));
517519
map.put(TIMESTAMP_TRUNC, map.get(FLOOR));
518520
map.put(TIME_TRUNC, map.get(FLOOR));
519521

520-
521522
defineMethod(LAST_DAY, "lastDay", NullPolicy.STRICT);
522523
map.put(DAYNAME,
523524
new PeriodNameImplementor("dayName",

core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -656,15 +656,20 @@ private SqlLibraryOperators() {
656656
TimeUnitRange.CENTURY,
657657
TimeUnitRange.DECADE,
658658
TimeUnitRange.YEAR,
659+
TimeUnitRange.QUARTER,
659660
TimeUnitRange.MONTH);
660661

661-
private static final Set<TimeUnitRange> DATE_UNITS =
662+
private static final Set<TimeUnitRange> DAY_UNITS =
662663
ImmutableSet.of(TimeUnitRange.WEEK,
663664
TimeUnitRange.DAY);
664665

666+
private static final Set<TimeUnitRange> DATE_UNITS =
667+
ImmutableSet.<TimeUnitRange>builder()
668+
.addAll(MONTH_UNITS).addAll(DAY_UNITS).build();
669+
665670
private static final Set<TimeUnitRange> TIMESTAMP_UNITS =
666671
ImmutableSet.<TimeUnitRange>builder()
667-
.addAll(MONTH_UNITS).addAll(DATE_UNITS).addAll(TIME_UNITS).build();
672+
.addAll(DATE_UNITS).addAll(TIME_UNITS).build();
668673

669674
/** The "TIMESTAMP_ADD(timestamp, interval)" function (BigQuery), the
670675
* two-argument variant of the built-in
@@ -707,6 +712,16 @@ private SqlLibraryOperators() {
707712
OperandTypes.family(SqlTypeFamily.TIME, SqlTypeFamily.TIME,
708713
SqlTypeFamily.ANY));
709714

715+
/** The "DATE_TRUNC(date, timeUnit)" function (BigQuery);
716+
* truncates a DATE value to the beginning of a timeUnit. */
717+
@LibraryOperator(libraries = {BIG_QUERY})
718+
public static final SqlFunction DATE_TRUNC =
719+
SqlBasicFunction.create("DATE_TRUNC",
720+
ReturnTypes.DATE_NULLABLE,
721+
OperandTypes.sequence("'DATE_TRUNC(<DATE>, <DATETIME_INTERVAL>)'",
722+
OperandTypes.DATE, OperandTypes.interval(DATE_UNITS)),
723+
SqlFunctionCategory.TIMEDATE);
724+
710725
/** The "TIME_TRUNC(time, timeUnit)" function (BigQuery);
711726
* truncates a TIME value to the beginning of a timeUnit. */
712727
@LibraryOperator(libraries = {BIG_QUERY})

core/src/main/java/org/apache/calcite/sql/type/IntervalOperandTypeChecker.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.calcite.sql.SqlNode;
2323
import org.apache.calcite.sql.SqlOperator;
2424
import org.apache.calcite.util.Static;
25+
import org.apache.calcite.util.Util;
2526

2627
import com.google.common.collect.ImmutableSet;
2728

@@ -45,8 +46,10 @@ public class IntervalOperandTypeChecker implements SqlSingleOperandTypeChecker {
4546
return true;
4647
}
4748
if (throwOnFailure) {
49+
final String name =
50+
Util.first(interval.timeFrameName, interval.timeUnitRange.name());
4851
throw callBinding.getValidator().newValidationError(operand,
49-
Static.RESOURCE.invalidTimeFrame(interval.timeUnitRange.name()));
52+
Static.RESOURCE.invalidTimeFrame(name));
5053
}
5154
}
5255
return false;

site/_docs/reference.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ DATABASE,
544544
**DATE**,
545545
DATETIME_INTERVAL_CODE,
546546
DATETIME_INTERVAL_PRECISION,
547+
DATE_TRUNC,
547548
**DAY**,
548549
DAYS,
549550
**DEALLOCATE**,
@@ -2609,14 +2610,15 @@ semantics.
26092610
| b m p | CONCAT(string [, string ]*) | Concatenates two or more strings
26102611
| m | COMPRESS(string) | Compresses a string using zlib compression and returns the result as a binary string.
26112612
| p | CONVERT_TIMEZONE(tz1, tz2, datetime) | Converts the timezone of *datetime* from *tz1* to *tz2*
2612-
| b | CURRENT_DATETIME([timezone]) | Returns the current time as a TIMESTAMP from *timezone*
2613+
| b | CURRENT_DATETIME([ timeZone ]) | Returns the current time as a TIMESTAMP from *timezone*
26132614
| m | DAYNAME(datetime) | Returns the name, in the connection's locale, of the weekday in *datetime*; for example, it returns '星期日' for both DATE '2020-02-10' and TIMESTAMP '2020-02-10 10:10:10'
26142615
| b | DATE(string) | Equivalent to `CAST(string AS DATE)`
26152616
| p q | DATEADD(timeUnit, integer, datetime) | Equivalent to `TIMESTAMPADD(timeUnit, integer, datetime)`
26162617
| p q | DATEDIFF(timeUnit, datetime, datetime2) | Equivalent to `TIMESTAMPDIFF(timeUnit, datetime, datetime2)`
26172618
| q | DATEPART(timeUnit, datetime) | Equivalent to `EXTRACT(timeUnit FROM datetime)`
26182619
| b | DATE_FROM_UNIX_DATE(integer) | Returns the DATE that is *integer* days after 1970-01-01
26192620
| p | DATE_PART(timeUnit, datetime) | Equivalent to `EXTRACT(timeUnit FROM datetime)`
2621+
| b | DATE_TRUNC(date, timeUnit) | Truncates *date* to the granularity of *timeUnit*, rounding to the beginning of the unit
26202622
| o | DECODE(value, value1, result1 [, valueN, resultN ]* [, default ]) | Compares *value* to each *valueN* value one by one; if *value* is equal to a *valueN*, returns the corresponding *resultN*, else returns *default*, or NULL if *default* is not specified
26212623
| p | DIFFERENCE(string, string) | Returns a measure of the similarity of two strings, namely the number of character positions that their `SOUNDEX` values have in common: 4 if the `SOUNDEX` values are same and 0 if the `SOUNDEX` values are totally different
26222624
| b | ENDS_WITH(string1, string2) | Returns whether *string2* is a suffix of *string1*
@@ -2667,10 +2669,10 @@ semantics.
26672669
| b | TIMESTAMP_MICROS(integer) | Returns the TIMESTAMP that is *integer* microseconds after 1970-01-01 00:00:00
26682670
| b | TIMESTAMP_MILLIS(integer) | Returns the TIMESTAMP that is *integer* milliseconds after 1970-01-01 00:00:00
26692671
| b | TIMESTAMP_SECONDS(integer) | Returns the TIMESTAMP that is *integer* seconds after 1970-01-01 00:00:00
2670-
| b | TIMESTAMP_TRUNC(timestamp, timeUnit) | Truncates a *timestamp* value to the granularity of *timeUnit*. The *timestamp* value is always rounded to the beginning of the *timeUnit*.
2672+
| b | TIMESTAMP_TRUNC(timestamp, timeUnit) | Truncates *timestamp* to the granularity of *timeUnit*, rounding to the beginning of the unit
26712673
| b | TIME_ADD(time, interval) | Adds *interval* to *time*, independent of any time zone
26722674
| b | TIME_DIFF(time, time2, timeUnit) | Returns the whole number of *timeUnit* between *time* and *time2*
2673-
| b | TIME_TRUNC(time, timeUnit) | Truncates a *time* value to the granularity of *timeUnit*. The *time* value is always rounded to the beginning of timeUnit, which can be one of the following: MILLISECOND, SECOND, MINUTE, HOUR.
2675+
| b | TIME_TRUNC(time, timeUnit) | Truncates *time* to the granularity of *timeUnit*, rounding to the beginning of the unit
26742676
| o p | TO_DATE(string, format) | Converts *string* to a date using the format *format*
26752677
| o p | TO_TIMESTAMP(string, format) | Converts *string* to a timestamp using the format *format*
26762678
| b o p | TRANSLATE(expr, fromString, toString) | Returns *expr* with all occurrences of each character in *fromString* replaced by its corresponding character in *toString*. Characters in *expr* that are not in *fromString* are not replaced

testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java

+18
Original file line numberDiff line numberDiff line change
@@ -8294,6 +8294,24 @@ private static void checkArrayConcatAggFuncFails(SqlOperatorFixture t) {
82948294
"2015-01-01 00:00:00", "TIMESTAMP(0) NOT NULL");
82958295
}
82968296

8297+
@Test void testDateTrunc() {
8298+
final SqlOperatorFixture f = fixture()
8299+
.withLibrary(SqlLibrary.BIG_QUERY)
8300+
.setFor(SqlLibraryOperators.DATE_TRUNC);
8301+
f.checkFails("date_trunc(date '2015-02-19', ^foo^)",
8302+
"'FOO' is not a valid time frame", false);
8303+
f.checkScalar("date_trunc(date '2015-02-19', day)",
8304+
"2015-02-19", "DATE NOT NULL");
8305+
f.checkScalar("date_trunc(date '2015-02-19', week)",
8306+
"2015-02-15", "DATE NOT NULL");
8307+
f.checkScalar("date_trunc(date '2015-02-19', month)",
8308+
"2015-02-01", "DATE NOT NULL");
8309+
f.checkScalar("date_trunc(date '2015-02-19', quarter)",
8310+
"2015-01-01", "DATE NOT NULL");
8311+
f.checkScalar("date_trunc(date '2015-02-19', year)",
8312+
"2015-01-01", "DATE NOT NULL");
8313+
}
8314+
82978315
@Test void testDenseRankFunc() {
82988316
final SqlOperatorFixture f = fixture();
82998317
f.setFor(SqlStdOperatorTable.DENSE_RANK, VM_FENNEL, VM_JAVA);

0 commit comments

Comments
 (0)