{ 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.
+/** 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}.
+ *
+ * The units are used in several functions, incuding CEIL, FLOOR, EXTRACT.
+ * Includes NANOSECOND, MILLISECOND, which were previously allowed in EXTRACT
+ * but not CEIL, FLOOR.
+ *
+ *
Includes {@code WEEK} and {@code WEEK(SUNDAY)} through
+ {@code WEEK(SATURDAY)}.
+ *
+ *
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 TimeUnitForExtract() :
-{
+SqlIntervalQualifier TimeUnitOrName() : {
+ final Span span;
+ final String w;
final TimeUnit unit;
+ final SqlIdentifier unitName;
}
{
- LOOKAHEAD(1)
+ LOOKAHEAD(2)
+ { return new SqlIntervalQualifier(TimeUnit.NANOSECOND, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.MICROSECOND, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.MILLISECOND, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.SECOND, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.MINUTE, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.HOUR, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.DAY, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.DOW, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.DOY, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.ISODOW, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.ISOYEAR, null, getPos()); }
+| { span = span(); }
(
- { return TimeUnit.NANOSECOND; }
- | { return TimeUnit.MICROSECOND; }
+ LOOKAHEAD(2)
+ w = weekdayName() {
+ return new SqlIntervalQualifier(w, span.end(this));
+ }
+ |
+ { return new SqlIntervalQualifier(TimeUnit.WEEK, null, getPos()); }
)
-|
- unit = TimeUnit() { return unit; }
+| { return new SqlIntervalQualifier(TimeUnit.MONTH, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.QUARTER, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.YEAR, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.EPOCH, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.DECADE, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.CENTURY, null, getPos()); }
+| { return new SqlIntervalQualifier(TimeUnit.MILLENNIUM, null, getPos()); }
+| unitName = SimpleIdentifier() {
+ return new SqlIntervalQualifier(unitName.getSimple(),
+ unitName.getParserPosition());
+ }
}
-/**
- * Parses a simple identifier as a TimeUnit.
- */
-TimeUnit TimeUnitIdentifier() :
+String weekdayName() :
{
- final List names = new ArrayList();
- final List positions = new ArrayList();
}
{
- AddIdentifierSegment(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)));
- }
+ { return "WEEK_SUNDAY"; }
+| { return "WEEK_MONDAY"; }
+| { return "WEEK_TUESDAY"; }
+| { return "WEEK_WEDNESDAY"; }
+| { return "WEEK_THURSDAY"; }
+| { return "WEEK_FRIDAY"; }
+| { return "WEEK_SATURDAY"; }
}
-TimeUnit TimestampInterval() :
-{}
-{
- { return TimeUnit.MICROSECOND; }
-| { return TimeUnit.MICROSECOND; }
-| { return TimeUnit.NANOSECOND; }
-| { return TimeUnit.NANOSECOND; }
-| { return TimeUnit.MICROSECOND; }
-| { return TimeUnit.SECOND; }
-| { return TimeUnit.SECOND; }
-| { return TimeUnit.MINUTE; }
-| { return TimeUnit.MINUTE; }
-| { return TimeUnit.HOUR; }
-| { return TimeUnit.HOUR; }
-| { return TimeUnit.DAY; }
-| { return TimeUnit.DAY; }
-| { return TimeUnit.WEEK; }
-| { return TimeUnit.WEEK; }
-| { return TimeUnit.MONTH; }
-| { return TimeUnit.MONTH; }
-| { return TimeUnit.QUARTER; }
-| { return TimeUnit.QUARTER; }
-| { return TimeUnit.YEAR; }
-| { return TimeUnit.YEAR; }
-}
-
-
-
/**
* Parses a dynamic parameter marker.
*/
@@ -5860,7 +5928,7 @@ SqlNode BuiltinFunctionCall() :
SqlNode e;
final Span s;
SqlDataTypeSpec dt;
- final TimeUnit unit;
+ final SqlIntervalQualifier unit;
final SqlNode node;
}
{
@@ -5879,8 +5947,8 @@ SqlNode BuiltinFunctionCall() :
}
|
{ s = span(); }
- unit = TimeUnitForExtract() {
- args.add(new SqlIntervalQualifier(unit, null, getPos()));
+ unit = TimeUnitOrName() {
+ args.add(unit);
}
AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
@@ -6017,11 +6085,21 @@ SqlNode BuiltinFunctionCall() :
{
return SqlStdOperatorTable.TRIM.createCall(s.end(this), args);
}
+ |
+ node = DateTruncFunctionCall() { return node; }
|
node = TimestampAddFunctionCall() { return node; }
|
node = TimestampDiffFunctionCall() { return node; }
|
+ node = TimestampDiff3FunctionCall() { return node; }
+ |
+ node = TimestampTruncFunctionCall() { return node; }
+ |
+ node = TimeDiffFunctionCall() { return node; }
+ |
+ node = TimeTruncFunctionCall() { return node; }
+ |
<#list (parser.builtinFunctionCallMethods!default.parser.builtinFunctionCallMethods) as method>
node = ${method} { return node; }
|
@@ -6291,7 +6369,6 @@ SqlNode JsonQueryWrapperBehavior() :
SqlCall JsonQueryFunctionCall() :
{
final SqlNode[] args = new SqlNode[6];
- SqlNode returningClause;
SqlNode e;
List commonSyntax;
final Span span;
@@ -6533,14 +6610,12 @@ SqlCall TimestampAddFunctionCall() :
{
final List args = new ArrayList();
final Span s;
- final TimeUnit interval;
+ final SqlIntervalQualifier unit;
}
{
{ s = span(); }
- interval = TimestampInterval() {
- args.add(SqlLiteral.createSymbol(interval, getPos()));
- }
+ unit = TimeUnitOrName() { args.add(unit); }
AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
@@ -6558,14 +6633,12 @@ SqlCall TimestampDiffFunctionCall() :
{
final List args = new ArrayList();
final Span s;
- final TimeUnit interval;
+ final SqlIntervalQualifier unit;
}
{
{ s = span(); }
- interval = TimestampInterval() {
- args.add(SqlLiteral.createSymbol(interval, getPos()));
- }
+ unit = TimeUnitOrName() { args.add(unit); }
AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
@@ -6576,11 +6649,127 @@ SqlCall TimestampDiffFunctionCall() :
}
}
+/**
+ * Parses a call to BigQuery's TIMESTAMP_DIFF.
+ *
+ * The difference between TIMESTAMPDIFF and TIMESTAMP_DIFF is the ordering of
+ * the parameters and the arrangement of the subtraction.
+ * TIMESTAMPDIFF uses (unit, timestamp1, timestamp2) with (t2 - t1), while
+ * TIMESTAMP_DIFF uses (timestamp1, timestamp2, unit) with (t1 - t2).
+ */
+SqlCall TimestampDiff3FunctionCall() :
+{
+ final List args = new ArrayList();
+ final Span s;
+ final SqlIntervalQualifier unit;
+}
+{
+ { s = span(); }
+
+ AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
+
+ AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
+
+ unit = TimeUnitOrName() { args.add(unit); }
+ {
+ return SqlLibraryOperators.TIMESTAMP_DIFF3.createCall(s.end(this), args);
+ }
+}
+
+/**
+ * Parses a call to DATE_TRUNC.
+ */
+SqlCall DateTruncFunctionCall() :
+{
+ final List args = new ArrayList();
+ final Span s;
+ final SqlIntervalQualifier unit;
+}
+{
+ { s = span(); }
+
+ AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
+
+ // A choice of arguments allows us to support both
+ // the BigQuery variant, e.g. "DATE_TRUNC(d, YEAR)",
+ // and the Redshift variant, e.g. "DATE_TRUNC('year', DATE '2008-09-08')".
+ (
+ unit = TimeUnitOrName() { args.add(unit); }
+ |
+ AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
+ )
+ {
+ return SqlLibraryOperators.DATE_TRUNC.createCall(s.end(this), args);
+ }
+}
+
+/**
+ * Parses a call to TIMESTAMP_TRUNC.
+ */
+SqlCall TimestampTruncFunctionCall() :
+{
+ final List args = new ArrayList();
+ final Span s;
+ final SqlIntervalQualifier unit;
+}
+{
+ { s = span(); }
+
+ AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
+
+ unit = TimeUnitOrName() { args.add(unit); }
+ {
+ return SqlLibraryOperators.TIMESTAMP_TRUNC.createCall(s.end(this), args);
+ }
+}
+
+/**
+ * Parses a call to BigQuery's TIME_DIFF.
+ */
+SqlCall TimeDiffFunctionCall() :
+{
+ final List args = new ArrayList();
+ final Span s;
+ final SqlIntervalQualifier unit;
+}
+{
+ { s = span(); }
+
+ AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
+
+ AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
+
+ unit = TimeUnitOrName() { args.add(unit); }
+ {
+ return SqlLibraryOperators.TIME_DIFF.createCall(s.end(this), args);
+ }
+}
+
+/**
+ * Parses a call to TIME_TRUNC.
+ */
+SqlCall TimeTruncFunctionCall() :
+{
+ final List args = new ArrayList();
+ final Span s;
+ final SqlIntervalQualifier unit;
+}
+{
+ { s = span(); }
+
+ AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
+
+ unit = TimeUnitOrName() { args.add(unit); }
+ {
+ return SqlLibraryOperators.TIME_TRUNC.createCall(s.end(this), args);
+ }
+}
+
/**
* Parses a call to a grouping function inside the GROUP BY clause,
* for example {@code TUMBLE(rowtime, INTERVAL '1' MINUTE)}.
*/
-SqlCall GroupByWindowingCall():
+SqlCall GroupByWindowingCall() :
{
final Span s;
final List args;
@@ -6875,16 +7064,15 @@ SqlNode StandardFloorCeilOptions(Span s, boolean floorFlag) :
{
SqlNode e;
final List args = new ArrayList();
- TimeUnit unit;
+ final SqlIntervalQualifier unit;
SqlCall function;
final Span s1;
}
{
AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
(
-
- unit = TimeUnit() {
- args.add(new SqlIntervalQualifier(unit, null, getPos()));
+ unit = TimeUnitOrName() {
+ args.add(unit);
}
)?
{
@@ -7069,6 +7257,36 @@ SqlNode JdbcFunctionCall() :
name = call.getOperator().getName();
args = new SqlNodeList(call.getOperandList(), getPos());
}
+ |
+ LOOKAHEAD(1)
+ call = DateTruncFunctionCall() {
+ name = call.getOperator().getName();
+ args = new SqlNodeList(call.getOperandList(), getPos());
+ }
+ |
+ LOOKAHEAD(1)
+ call = TimestampTruncFunctionCall() {
+ name = call.getOperator().getName();
+ args = new SqlNodeList(call.getOperandList(), getPos());
+ }
+ |
+ LOOKAHEAD(1)
+ call = TimeTruncFunctionCall() {
+ name = call.getOperator().getName();
+ args = new SqlNodeList(call.getOperandList(), getPos());
+ }
+ |
+ LOOKAHEAD(1)
+ call = TimestampDiff3FunctionCall() {
+ name = call.getOperator().getName();
+ args = new SqlNodeList(call.getOperandList(), getPos());
+ }
+ |
+ LOOKAHEAD(1)
+ call = TimeDiffFunctionCall() {
+ name = call.getOperator().getName();
+ args = new SqlNodeList(call.getOperandList(), getPos());
+ }
|
LOOKAHEAD(3)
call = TimestampDiffFunctionCall() {
@@ -7438,6 +7656,8 @@ SqlPostfixOperator PostfixRowOperator() :
| < DATA: "DATA" >
| < DATABASE: "DATABASE" >
| < DATE: "DATE" >
+| < DATE_TRUNC: "DATE_TRUNC" >
+| < DATETIME: "DATETIME" >
| < DATETIME_INTERVAL_CODE: "DATETIME_INTERVAL_CODE" >
| < DATETIME_INTERVAL_PRECISION: "DATETIME_INTERVAL_PRECISION" >
| < DAY: "DAY" >
@@ -7522,6 +7742,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < FRAC_SECOND: "FRAC_SECOND" >
| < FRAME_ROW: "FRAME_ROW" >
| < FREE: "FREE" >
+| < FRIDAY: "FRIDAY" >
| < FROM: "FROM" > { beforeTableName(); }
| < FULL: "FULL" >
| < FUNCTION: "FUNCTION" >
@@ -7640,6 +7861,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < MOD: "MOD" >
| < MODIFIES: "MODIFIES" >
| < MODULE: "MODULE" >
+| < MONDAY: "MONDAY" >
| < MONTH: "MONTH" >
| < MONTHS: "MONTHS" >
| < MORE_: "MORE" >
@@ -7737,6 +7959,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < PROCEDURE: "PROCEDURE" >
| < PUBLIC: "PUBLIC" >
| < QUARTER: "QUARTER" >
+| < QUARTERS: "QUARTERS" >
| < RANGE: "RANGE" >
| < RANK: "RANK" >
| < READ: "READ" >
@@ -7786,6 +8009,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < ROW_NUMBER: "ROW_NUMBER" >
| < ROWS: "ROWS" >
| < RUNNING: "RUNNING" >
+| < SATURDAY: "SATURDAY" >
| < SAVEPOINT: "SAVEPOINT" >
| < SCALAR: "SCALAR" >
| < SCALE: "SCALE" >
@@ -7899,6 +8123,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < SUBSTRING_REGEX: "SUBSTRING_REGEX" >
| < SUCCEEDS: "SUCCEEDS" >
| < SUM: "SUM" >
+| < SUNDAY: "SUNDAY" >
| < SYMMETRIC: "SYMMETRIC" >
| < SYSTEM: "SYSTEM" >
| < SYSTEM_TIME: "SYSTEM_TIME" >
@@ -7908,11 +8133,16 @@ SqlPostfixOperator PostfixRowOperator() :
| < TABLESAMPLE: "TABLESAMPLE" >
| < TEMPORARY: "TEMPORARY" >
| < THEN: "THEN" >
+| < THURSDAY: "THURSDAY" >
| < TIES: "TIES" >
| < TIME: "TIME" >
+| < TIME_DIFF: "TIME_DIFF" >
+| < TIME_TRUNC: "TIME_TRUNC" >
| < TIMESTAMP: "TIMESTAMP" >
| < TIMESTAMPADD: "TIMESTAMPADD" >
| < TIMESTAMPDIFF: "TIMESTAMPDIFF" >
+| < TIMESTAMP_DIFF: "TIMESTAMP_DIFF" >
+| < TIMESTAMP_TRUNC: "TIMESTAMP_TRUNC" >
| < TIMEZONE_HOUR: "TIMEZONE_HOUR" >
| < TIMEZONE_MINUTE: "TIMEZONE_MINUTE" >
| < TINYINT: "TINYINT" >
@@ -7937,6 +8167,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < TRIM_ARRAY: "TRIM_ARRAY" >
| < TRUE: "TRUE" >
| < TRUNCATE: "TRUNCATE" >
+| < TUESDAY: "TUESDAY" >
| < TUMBLE: "TUMBLE" >
| < TYPE: "TYPE" >
| < UESCAPE: "UESCAPE" >
@@ -7974,7 +8205,9 @@ SqlPostfixOperator PostfixRowOperator() :
| < VERSION: "VERSION" >
| < VERSIONING: "VERSIONING" >
| < VIEW: "VIEW" >
+| < WEDNESDAY: "WEDNESDAY" >
| < WEEK: "WEEK" >
+| < WEEKS: "WEEKS" >
| < WHEN: "WHEN" >
| < WHENEVER: "WHENEVER" >
| < WHERE: "WHERE" >
@@ -8108,6 +8341,8 @@ void NonReservedKeyWord2of3() :
< PREFIXED_STRING_LITERAL: ("_" | "N") >
|
< UNICODE_STRING_LITERAL: "U" "&" >
+|
+ < C_STYLE_ESCAPED_STRING_LITERAL: "E" ( (~["'", "\\"]) | ("\\" ~[]) | "''")* >
|
< #CHARSETNAME: (["a"-"z","A"-"Z","0"-"9"])
(["a"-"z","A"-"Z","0"-"9",":",".","-","_"])*
diff --git a/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/validate/FlinkSqlConformance.java b/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/validate/FlinkSqlConformance.java
index a04ce07d1cd7a..b1235d6a42c46 100644
--- a/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/validate/FlinkSqlConformance.java
+++ b/flink-table/flink-sql-parser/src/main/java/org/apache/flink/sql/parser/validate/FlinkSqlConformance.java
@@ -166,4 +166,14 @@ public boolean allowQualifyingCommonColumn() {
public SqlLibrary semantics() {
return SqlConformanceEnum.DEFAULT.semantics();
}
+
+ @Override
+ public boolean allowCoercionStringToArray() {
+ return SqlConformanceEnum.DEFAULT.allowCoercionStringToArray();
+ }
+
+ @Override
+ public boolean isValueAllowed() {
+ return true;
+ }
}
diff --git a/flink-table/flink-sql-parser/src/test/java/org/apache/flink/sql/parser/FlinkSqlParserImplTest.java b/flink-table/flink-sql-parser/src/test/java/org/apache/flink/sql/parser/FlinkSqlParserImplTest.java
index 4245ff492ccde..c78ae5f2bdf9d 100644
--- a/flink-table/flink-sql-parser/src/test/java/org/apache/flink/sql/parser/FlinkSqlParserImplTest.java
+++ b/flink-table/flink-sql-parser/src/test/java/org/apache/flink/sql/parser/FlinkSqlParserImplTest.java
@@ -2131,8 +2131,8 @@ void testInsertPartitionSpecs() {
"INSERT INTO `EMPS` "
+ "PARTITION (`X` = 'ab', `Y` = 'bc')\n"
+ "(`X`, `Y`)\n"
- + "(SELECT *\n"
- + "FROM `EMPS`)";
+ + "SELECT *\n"
+ + "FROM `EMPS`";
sql(sql1).ok(expected);
final String sql2 =
"insert into emp\n"
@@ -2148,8 +2148,8 @@ void testInsertPartitionSpecs() {
+ "PARTITION (`EMPNO` = '1', `JOB` = 'job')\n"
+ "(`EMPNO`, `ENAME`, `JOB`, `MGR`, `HIREDATE`, `SAL`,"
+ " `COMM`, `DEPTNO`, `SLACKER`)\n"
- + "(SELECT 'nom', 0, TIMESTAMP '1970-01-01 00:00:00', 1, 1, 1, FALSE\n"
- + "FROM (VALUES (ROW('a'))))");
+ + "SELECT 'nom', 0, TIMESTAMP '1970-01-01 00:00:00', 1, 1, 1, FALSE\n"
+ + "FROM (VALUES (ROW('a')))");
final String sql3 =
"insert into empnullables\n"
+ "partition(ename='b')\n"
@@ -2160,8 +2160,8 @@ void testInsertPartitionSpecs() {
"INSERT INTO `EMPNULLABLES` "
+ "PARTITION (`ENAME` = 'b')\n"
+ "(`EMPNO`, `ENAME`)\n"
- + "(SELECT 1\n"
- + "FROM (VALUES (ROW('a'))))");
+ + "SELECT 1\n"
+ + "FROM (VALUES (ROW('a')))");
}
@Test
@@ -2170,8 +2170,8 @@ void testInsertCaseSensitivePartitionSpecs() {
"INSERT INTO `emps` "
+ "PARTITION (`x` = 'ab', `y` = 'bc')\n"
+ "(`x`, `y`)\n"
- + "(SELECT *\n"
- + "FROM `EMPS`)";
+ + "SELECT *\n"
+ + "FROM `EMPS`";
sql("insert into \"emps\" "
+ "partition (\"x\"='ab', \"y\"='bc')(\"x\",\"y\") select * from emps")
.ok(expected);
@@ -2183,8 +2183,8 @@ void testInsertExtendedColumnAsStaticPartition1() {
"INSERT INTO `EMPS` EXTEND (`Z` BOOLEAN) "
+ "PARTITION (`Z` = 'ab')\n"
+ "(`X`, `Y`)\n"
- + "(SELECT *\n"
- + "FROM `EMPS`)";
+ + "SELECT *\n"
+ + "FROM `EMPS`";
sql("insert into emps(z boolean) partition (z='ab') (x,y) select * from emps").ok(expected);
}
@@ -2204,7 +2204,7 @@ void testInsertExtendedColumnAsStaticPartition2() {
void testInsertOverwrite() {
// non-partitioned
final String sql = "INSERT OVERWRITE myDB.myTbl SELECT * FROM src";
- final String expected = "INSERT OVERWRITE `MYDB`.`MYTBL`\n" + "(SELECT *\n" + "FROM `SRC`)";
+ final String expected = "INSERT OVERWRITE `MYDB`.`MYTBL`\n" + "SELECT *\n" + "FROM `SRC`";
sql(sql).ok(expected);
// partitioned
@@ -2213,8 +2213,8 @@ void testInsertOverwrite() {
"INSERT OVERWRITE `MYTBL` "
+ "PARTITION (`P1` = 'v1', `P2` = 'v2')\n"
+ "\n"
- + "(SELECT *\n"
- + "FROM `SRC`)";
+ + "SELECT *\n"
+ + "FROM `SRC`";
sql(sql1).ok(expected1);
}
@@ -2552,12 +2552,12 @@ void testExecuteStatementSet() {
.ok(
"EXECUTE STATEMENT SET BEGIN\n"
+ "INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)\n"
+ + "SELECT *\n"
+ + "FROM `T2`\n"
+ ";\n"
+ "INSERT INTO `T2`\n"
- + "(SELECT *\n"
- + "FROM `T3`)\n"
+ + "SELECT *\n"
+ + "FROM `T3`\n"
+ ";\n"
+ "END");
}
@@ -2568,12 +2568,12 @@ void testExplainStatementSet() {
.ok(
"EXPLAIN STATEMENT SET BEGIN\n"
+ "INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)\n"
+ + "SELECT *\n"
+ + "FROM `T2`\n"
+ ";\n"
+ "INSERT INTO `T2`\n"
- + "(SELECT *\n"
- + "FROM `T3`)\n"
+ + "SELECT *\n"
+ + "FROM `T3`\n"
+ ";\n"
+ "END");
}
@@ -2617,11 +2617,11 @@ void testExplainEstimatedCost() {
void testExplainUnion() {
String sql = "explain estimated_cost select * from emps union all select * from emps";
String expected =
- "EXPLAIN ESTIMATED_COST (SELECT *\n"
+ "EXPLAIN ESTIMATED_COST SELECT *\n"
+ "FROM `EMPS`\n"
+ "UNION ALL\n"
+ "SELECT *\n"
- + "FROM `EMPS`)";
+ + "FROM `EMPS`";
this.sql(sql).ok(expected);
}
@@ -2681,13 +2681,13 @@ void testExplainAllDetails() {
@Test
void testExplainInsert() {
- String expected = "EXPLAIN INSERT INTO `EMPS1`\n" + "(SELECT *\n" + "FROM `EMPS2`)";
+ String expected = "EXPLAIN INSERT INTO `EMPS1`\n" + "SELECT *\n" + "FROM `EMPS2`";
this.sql("explain plan for insert into emps1 select * from emps2").ok(expected);
}
@Test
void testExecuteInsert() {
- String expected = "EXECUTE INSERT INTO `EMPS1`\n" + "(SELECT *\n" + "FROM `EMPS2`)";
+ String expected = "EXECUTE INSERT INTO `EMPS1`\n" + "SELECT *\n" + "FROM `EMPS2`";
this.sql("execute insert into emps1 select * from emps2").ok(expected);
}
@@ -2705,30 +2705,30 @@ void testCompilePlan() {
sql("compile plan './test.json' for insert into t1 select * from t2")
.ok(
"COMPILE PLAN './test.json' FOR INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)");
+ + "SELECT *\n"
+ + "FROM `T2`");
sql("compile plan './test.json' if not exists for insert into t1 select * from t2")
.ok(
"COMPILE PLAN './test.json' IF NOT EXISTS FOR INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)");
+ + "SELECT *\n"
+ + "FROM `T2`");
sql("compile plan 'file:///foo/bar/test.json' if not exists for insert into t1 select * from t2")
.ok(
"COMPILE PLAN 'file:///foo/bar/test.json' IF NOT EXISTS FOR INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)");
+ + "SELECT *\n"
+ + "FROM `T2`");
sql("compile plan './test.json' for statement set "
+ "begin insert into t1 select * from t2; insert into t2 select * from t3; end")
.ok(
"COMPILE PLAN './test.json' FOR STATEMENT SET BEGIN\n"
+ "INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)\n"
+ + "SELECT *\n"
+ + "FROM `T2`\n"
+ ";\n"
+ "INSERT INTO `T2`\n"
- + "(SELECT *\n"
- + "FROM `T3`)\n"
+ + "SELECT *\n"
+ + "FROM `T3`\n"
+ ";\n"
+ "END");
sql("compile plan './test.json' if not exists for statement set "
@@ -2736,12 +2736,12 @@ void testCompilePlan() {
.ok(
"COMPILE PLAN './test.json' IF NOT EXISTS FOR STATEMENT SET BEGIN\n"
+ "INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)\n"
+ + "SELECT *\n"
+ + "FROM `T2`\n"
+ ";\n"
+ "INSERT INTO `T2`\n"
- + "(SELECT *\n"
- + "FROM `T3`)\n"
+ + "SELECT *\n"
+ + "FROM `T3`\n"
+ ";\n"
+ "END");
@@ -2750,12 +2750,12 @@ void testCompilePlan() {
.ok(
"COMPILE PLAN 'file:///foo/bar/test.json' IF NOT EXISTS FOR STATEMENT SET BEGIN\n"
+ "INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)\n"
+ + "SELECT *\n"
+ + "FROM `T2`\n"
+ ";\n"
+ "INSERT INTO `T2`\n"
- + "(SELECT *\n"
- + "FROM `T3`)\n"
+ + "SELECT *\n"
+ + "FROM `T3`\n"
+ ";\n"
+ "END");
}
@@ -2765,27 +2765,27 @@ void testCompileAndExecutePlan() {
sql("compile and execute plan './test.json' for insert into t1 select * from t2")
.ok(
"COMPILE AND EXECUTE PLAN './test.json' FOR INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)");
+ + "SELECT *\n"
+ + "FROM `T2`");
sql("compile and execute plan './test.json' for statement set "
+ "begin insert into t1 select * from t2; insert into t2 select * from t3; end")
.ok(
"COMPILE AND EXECUTE PLAN './test.json' FOR STATEMENT SET BEGIN\n"
+ "INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)\n"
+ + "SELECT *\n"
+ + "FROM `T2`\n"
+ ";\n"
+ "INSERT INTO `T2`\n"
- + "(SELECT *\n"
- + "FROM `T3`)\n"
+ + "SELECT *\n"
+ + "FROM `T3`\n"
+ ";\n"
+ "END");
sql("compile and execute plan 'file:///foo/bar/test.json' for insert into t1 select * from t2")
.ok(
"COMPILE AND EXECUTE PLAN 'file:///foo/bar/test.json' FOR INSERT INTO `T1`\n"
- + "(SELECT *\n"
- + "FROM `T2`)");
+ + "SELECT *\n"
+ + "FROM `T2`");
}
@Test
@@ -2829,10 +2829,10 @@ void testShowJars() {
@Test
void testSetReset() {
- sql("SET").ok("SET");
- sql("SET 'test-key' = 'test-value'").ok("SET 'test-key' = 'test-value'");
- sql("RESET").ok("RESET");
- sql("RESET 'test-key'").ok("RESET 'test-key'");
+ sql("SET").same();
+ sql("SET 'test-key' = 'test-value'").same();
+ sql("RESET").same();
+ sql("RESET 'test-key'").same();
}
@Test
@@ -3121,10 +3121,9 @@ void testShowJobs() {
@Test
void testStopJob() {
- sql("STOP JOB 'myjob'").ok("STOP JOB 'myjob'");
- sql("STOP JOB 'myjob' WITH SAVEPOINT").ok("STOP JOB 'myjob' WITH SAVEPOINT");
- sql("STOP JOB 'myjob' WITH SAVEPOINT WITH DRAIN")
- .ok("STOP JOB 'myjob' WITH SAVEPOINT WITH DRAIN");
+ sql("STOP JOB 'myjob'").same();
+ sql("STOP JOB 'myjob' WITH SAVEPOINT").same();
+ sql("STOP JOB 'myjob' WITH SAVEPOINT WITH DRAIN").same();
sql("STOP JOB 'myjob' ^WITH DRAIN^")
.fails("WITH DRAIN could only be used after WITH SAVEPOINT.");
sql("STOP JOB 'myjob' ^WITH DRAIN^ WITH SAVEPOINT")
@@ -3133,7 +3132,7 @@ void testStopJob() {
@Test
void testDescribeJob() {
- sql("DESCRIBE JOB 'myjob'").ok("DESCRIBE JOB 'myjob'");
+ sql("DESCRIBE JOB 'myjob'").same();
sql("DESC JOB 'myjob'").ok("DESCRIBE JOB 'myjob'");
}
diff --git a/flink-table/flink-table-calcite-bridge/pom.xml b/flink-table/flink-table-calcite-bridge/pom.xml
index 3cb2101c2b90c..3c57ecbaab170 100644
--- a/flink-table/flink-table-calcite-bridge/pom.xml
+++ b/flink-table/flink-table-calcite-bridge/pom.xml
@@ -45,23 +45,23 @@ under the License.
${calcite.version}
diff --git a/flink-table/flink-table-planner/pom.xml b/flink-table/flink-table-planner/pom.xml
index dadcb0da7ef4b..2458256fb4042 100644
--- a/flink-table/flink-table-planner/pom.xml
+++ b/flink-table/flink-table-planner/pom.xml
@@ -77,8 +77,12 @@ under the License.
value-annotations
2.8.8
-
-
+
+ org.apache.commons
+ commons-math3
+ 3.6.1
+ ${flink.markBundledAsOptional}
+
org.codehaus.janino
commons-compiler
@@ -320,6 +324,7 @@ under the License.
com.google.guava:failureaccess
commons-codec:commons-codec
commons-io:commons-io
+ org.apache.commons:commons-math3
org.checkerframework:checker-qual
@@ -348,6 +353,10 @@ under the License.
org.apache.commons.io
org.apache.flink.calcite.shaded.org.apache.commons.io
+
+ org.apache.commons.math3
+ org.apache.flink.calcite.shaded.org.apache.commons.math3
+
org.checkerframework
org.apache.flink.calcite.shaded.org.checkerframework
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalSnapshot.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalSnapshot.java
index f2e974d3dd383..39199fae11246 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalSnapshot.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/logical/LogicalSnapshot.java
@@ -24,6 +24,7 @@
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.RelCollationTraitDef;
import org.apache.calcite.rel.RelDistributionTraitDef;
+import org.apache.calcite.rel.RelInput;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Snapshot;
import org.apache.calcite.rel.hint.RelHint;
@@ -40,12 +41,18 @@
* Sub-class of {@link org.apache.calcite.rel.core.Snapshot} not targeted at any particular engine
* or calling convention. The class was copied over because of * CALCITE-4554. *
*
- * Line 106 ~ 117: Calcite only supports timestamp type as period type, but Flink supports both
+ *
Line 114 ~ 124: Calcite only supports timestamp type as period type, but Flink supports both
* Timestamp and TimestampLtz. Should be removed once calcite support TimestampLtz as period type.
*/
public class LogicalSnapshot extends Snapshot {
// ~ Constructors -----------------------------------------------------------
+
+ /** Creates a LogicalSnapshot by parsing serialized output. */
+ public LogicalSnapshot(RelInput input) {
+ super(input);
+ }
+
/**
* Creates a LogicalSnapshot.
*
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
index a11e67ca956bf..22c8f1e3b62f9 100644
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
@@ -27,6 +27,7 @@
import org.apache.calcite.plan.volcano.RelSubset;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.Correlate;
import org.apache.calcite.rel.core.Exchange;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.Intersect;
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexUtil.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexUtil.java
new file mode 100644
index 0000000000000..01dfc2409db0b
--- /dev/null
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexUtil.java
@@ -0,0 +1,3176 @@
+/*
+ * 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.rex;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Range;
+import org.apache.calcite.DataContexts;
+import org.apache.calcite.linq4j.function.Predicate1;
+import org.apache.calcite.plan.RelOptPredicateList;
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeFamily;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexTableInputRef.RelTableRef;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeFamily;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.validate.SqlValidatorUtil;
+import org.apache.calcite.util.ControlFlowException;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.Litmus;
+import org.apache.calcite.util.Pair;
+import org.apache.calcite.util.RangeSets;
+import org.apache.calcite.util.Sarg;
+import org.apache.calcite.util.Util;
+import org.apache.calcite.util.mapping.Mappings;
+import org.apiguardian.api.API;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Default implementation of {@link org.apache.calcite.rex.RexUtil}, the class was copied over
+ * because of current Calcite way of inferring constants from IS NOT DISTINCT FROM clashes with
+ * filter push down.
+ *
+ *
Lines 397 ~ 399, Use Calcite 1.32.0 behavior for {@link RexUtil#gatherConstraints(Class,
+ * RexNode, Map, Set, RexBuilder)}.
+ */
+public class RexUtil {
+
+ /** Executor for a bit of constant reduction. The user can pass in another executor. */
+ public static final RexExecutor EXECUTOR = new RexExecutorImpl(DataContexts.EMPTY);
+
+ private RexUtil() {}
+
+ // ~ Methods ----------------------------------------------------------------
+
+ /**
+ * Returns a guess for the selectivity of an expression.
+ *
+ * @param exp expression of interest, or null for none (implying a selectivity of 1.0)
+ * @return guessed selectivity
+ */
+ public static double getSelectivity(@Nullable RexNode exp) {
+ if ((exp == null) || exp.isAlwaysTrue()) {
+ return 1d;
+ }
+ return 0.1d;
+ }
+
+ /**
+ * Generates a cast from one row type to another.
+ *
+ * @param rexBuilder RexBuilder to use for constructing casts
+ * @param lhsRowType target row type
+ * @param rhsRowType source row type; fields must be 1-to-1 with lhsRowType, in same order
+ * @return cast expressions
+ */
+ public static List generateCastExpressions(
+ RexBuilder rexBuilder, RelDataType lhsRowType, RelDataType rhsRowType) {
+ final List fieldList = rhsRowType.getFieldList();
+ int n = fieldList.size();
+ assert n == lhsRowType.getFieldCount()
+ : "field count: lhs [" + lhsRowType + "] rhs [" + rhsRowType + "]";
+ List rhsExps = new ArrayList<>();
+ for (RelDataTypeField field : fieldList) {
+ rhsExps.add(rexBuilder.makeInputRef(field.getType(), field.getIndex()));
+ }
+ return generateCastExpressions(rexBuilder, lhsRowType, rhsExps);
+ }
+
+ /**
+ * Generates a cast for a row type.
+ *
+ * @param rexBuilder RexBuilder to use for constructing casts
+ * @param lhsRowType target row type
+ * @param rhsExps expressions to be cast
+ * @return cast expressions
+ */
+ public static List generateCastExpressions(
+ RexBuilder rexBuilder, RelDataType lhsRowType, List rhsExps) {
+ List lhsFields = lhsRowType.getFieldList();
+ List castExps = new ArrayList<>();
+ for (Pair pair : Pair.zip(lhsFields, rhsExps, true)) {
+ RelDataTypeField lhsField = pair.left;
+ RelDataType lhsType = lhsField.getType();
+ final RexNode rhsExp = pair.right;
+ RelDataType rhsType = rhsExp.getType();
+ if (lhsType.equals(rhsType)) {
+ castExps.add(rhsExp);
+ } else {
+ castExps.add(rexBuilder.makeCast(lhsType, rhsExp));
+ }
+ }
+ return castExps;
+ }
+
+ /**
+ * Returns whether a node represents the NULL value.
+ *
+ * Examples:
+ *
+ *
+ * - For {@link org.apache.calcite.rex.RexLiteral} Unknown, returns false.
+ *
- For
CAST(NULL AS type)
, returns true if
+ * allowCast
is true, false otherwise.
+ * - For
CAST(CAST(NULL AS type) AS type))
, returns false.
+ *
+ */
+ public static boolean isNullLiteral(RexNode node, boolean allowCast) {
+ if (node instanceof RexLiteral) {
+ RexLiteral literal = (RexLiteral) node;
+ if (literal.getTypeName() == SqlTypeName.NULL) {
+ assert null == literal.getValue();
+ return true;
+ } else {
+ // We don't regard UNKNOWN -- SqlLiteral(null,Boolean) -- as
+ // NULL.
+ return false;
+ }
+ }
+ if (allowCast) {
+ if (node.isA(SqlKind.CAST)) {
+ RexCall call = (RexCall) node;
+ if (isNullLiteral(call.operands.get(0), false)) {
+ // node is "CAST(NULL as type)"
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether a node represents the NULL value or a series of nested {@code CAST(NULL AS
+ * type)} calls. For example: isNull(CAST(CAST(NULL as INTEGER) AS VARCHAR(1)))
+ * returns {@code true}.
+ */
+ public static boolean isNull(RexNode expr) {
+ switch (expr.getKind()) {
+ case LITERAL:
+ return ((RexLiteral) expr).getValue2() == null;
+ case CAST:
+ return isNull(((RexCall) expr).operands.get(0));
+ default:
+ return false;
+ }
+ }
+
+ /** Returns whether a node represents a {@link SqlTypeName#SYMBOL} literal. */
+ public static boolean isSymbolLiteral(RexNode expr) {
+ switch (expr.getKind()) {
+ case LITERAL:
+ return ((RexLiteral) expr).getTypeName() == SqlTypeName.SYMBOL;
+ case CAST:
+ return isSymbolLiteral(((RexCall) expr).operands.get(0));
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether a node represents a literal.
+ *
+ * Examples:
+ *
+ *
+ * - For
CAST(literal AS type)
, returns true if
+ * allowCast
is true, false otherwise.
+ * - For
CAST(CAST(literal AS type) AS type))
, returns false.
+ *
+ *
+ * @param node The node, never null.
+ * @param allowCast whether to regard CAST(literal) as a literal
+ * @return Whether the node is a literal
+ */
+ public static boolean isLiteral(RexNode node, boolean allowCast) {
+ assert node != null;
+ if (node.isA(SqlKind.LITERAL)) {
+ return true;
+ }
+ if (allowCast) {
+ if (node.isA(SqlKind.CAST)) {
+ RexCall call = (RexCall) node;
+ if (isLiteral(call.operands.get(0), false)) {
+ // node is "CAST(literal as type)"
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether every expression in a list is a literal.
+ *
+ * @param expressionOperands list of expressions to check
+ * @return true if every expression from the specified list is literal.
+ */
+ public static boolean allLiterals(List expressionOperands) {
+ for (RexNode rexNode : expressionOperands) {
+ if (!isLiteral(rexNode, true)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether a node represents an input reference or field access.
+ *
+ * @param node The node, never null.
+ * @param allowCast whether to regard CAST(x) as true
+ * @return Whether the node is a reference or access
+ */
+ public static boolean isReferenceOrAccess(RexNode node, boolean allowCast) {
+ assert node != null;
+ if (node instanceof RexInputRef || node instanceof RexFieldAccess) {
+ return true;
+ }
+ if (allowCast) {
+ if (node.isA(SqlKind.CAST)) {
+ RexCall call = (RexCall) node;
+ return isReferenceOrAccess(call.operands.get(0), false);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether an expression is a cast just for the purposes of nullability, not changing
+ * any other aspect of the type.
+ */
+ public static boolean isNullabilityCast(RelDataTypeFactory typeFactory, RexNode node) {
+ switch (node.getKind()) {
+ case CAST:
+ final RexCall call = (RexCall) node;
+ final RexNode arg0 = call.getOperands().get(0);
+ return SqlTypeUtil.equalSansNullability(
+ typeFactory, arg0.getType(), call.getType());
+ default:
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Removes any casts that change nullability but not type.
+ *
+ * For example, {@code CAST(1 = 0 AS BOOLEAN)} becomes {@code 1 = 0}.
+ */
+ public static RexNode removeNullabilityCast(RelDataTypeFactory typeFactory, RexNode node) {
+ while (isNullabilityCast(typeFactory, node)) {
+ node = ((RexCall) node).operands.get(0);
+ }
+ return node;
+ }
+
+ /**
+ * Removes any casts.
+ *
+ *
For example, {@code CAST('1' AS INTEGER)} becomes {@code '1'}.
+ */
+ public static RexNode removeCast(RexNode e) {
+ for (; ; ) {
+ switch (e.getKind()) {
+ case CAST:
+ e = ((RexCall) e).operands.get(0);
+ break;
+ default:
+ return e;
+ }
+ }
+ }
+
+ /**
+ * Creates a map containing each (e, constant) pair that occurs within a predicate list.
+ *
+ * @param clazz Class of expression that is considered constant
+ * @param rexBuilder Rex builder
+ * @param predicates Predicate list
+ * @param what to consider a constant: {@link RexLiteral} to use a narrow definition of
+ * constant, or {@link RexNode} to use {@link RexUtil#isConstant(RexNode)}
+ * @return Map from values to constants
+ */
+ public static ImmutableMap predicateConstants(
+ Class clazz, RexBuilder rexBuilder, List predicates) {
+ // We cannot use an ImmutableMap.Builder here. If there are multiple entries
+ // with the same key (e.g. "WHERE deptno = 1 AND deptno = 2"), it doesn't
+ // matter which we take, so the latter will replace the former.
+ // The basic idea is to find all the pairs of RexNode = RexLiteral
+ // (1) If 'predicates' contain a non-EQUALS, we bail out.
+ // (2) It is OK if a RexNode is equal to the same RexLiteral several times,
+ // (e.g. "WHERE deptno = 1 AND deptno = 1")
+ // (3) It will return false if there are inconsistent constraints (e.g.
+ // "WHERE deptno = 1 AND deptno = 2")
+ final Map map = new HashMap<>();
+ final Set excludeSet = new HashSet<>();
+ for (RexNode predicate : predicates) {
+ gatherConstraints(clazz, predicate, map, excludeSet, rexBuilder);
+ }
+ final ImmutableMap.Builder builder = ImmutableMap.builder();
+ for (Map.Entry entry : map.entrySet()) {
+ RexNode rexNode = entry.getKey();
+ if (!overlap(rexNode, excludeSet)) {
+ builder.put(rexNode, entry.getValue());
+ }
+ }
+ return builder.build();
+ }
+
+ private static boolean overlap(RexNode rexNode, Set set) {
+ if (rexNode instanceof RexCall) {
+ for (RexNode r : ((RexCall) rexNode).getOperands()) {
+ if (overlap(r, set)) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return set.contains(rexNode);
+ }
+ }
+
+ /** Tries to decompose the RexNode which is a RexCall into non-literal RexNodes. */
+ private static void decompose(Set set, RexNode rexNode) {
+ if (rexNode instanceof RexCall) {
+ for (RexNode r : ((RexCall) rexNode).getOperands()) {
+ decompose(set, r);
+ }
+ } else if (!(rexNode instanceof RexLiteral)) {
+ set.add(rexNode);
+ }
+ }
+
+ private static void gatherConstraints(
+ Class clazz,
+ RexNode predicate,
+ Map map,
+ Set excludeSet,
+ RexBuilder rexBuilder) {
+ final RexNode left;
+ final RexNode right;
+ switch (predicate.getKind()) {
+ case EQUALS:
+ // FLINK BEGIN MODIFICATION
+ // case IS_NOT_DISTINCT_FROM:
+ // FLINK END MODIFICATION
+ left = ((RexCall) predicate).getOperands().get(0);
+ right = ((RexCall) predicate).getOperands().get(1);
+ break;
+
+ case IS_NULL:
+ left = ((RexCall) predicate).getOperands().get(0);
+ if (!left.getType().isNullable()) {
+ // There's no sense in inferring $0=null when $0 is not nullable
+ return;
+ }
+ right = rexBuilder.makeNullLiteral(left.getType());
+ break;
+
+ default:
+ decompose(excludeSet, predicate);
+ return;
+ }
+ // Note that literals are immutable too, and they can only be compared
+ // through values.
+ gatherConstraint(clazz, left, right, map, excludeSet, rexBuilder);
+ gatherConstraint(clazz, right, left, map, excludeSet, rexBuilder);
+ }
+
+ private static void gatherConstraint(
+ Class clazz,
+ RexNode left,
+ RexNode right,
+ Map map,
+ Set excludeSet,
+ RexBuilder rexBuilder) {
+ if (!clazz.isInstance(right)) {
+ return;
+ }
+ if (!isConstant(right)) {
+ return;
+ }
+ C constant = clazz.cast(right);
+ if (excludeSet.contains(left)) {
+ return;
+ }
+ final C existedValue = map.get(left);
+ if (existedValue == null) {
+ switch (left.getKind()) {
+ case CAST:
+ // Convert "CAST(c) = literal" to "c = literal", as long as it is a
+ // widening cast.
+ final RexNode operand = ((RexCall) left).getOperands().get(0);
+ if (canAssignFrom(
+ left.getType(), operand.getType(), rexBuilder.getTypeFactory())) {
+ final RexNode castRight = rexBuilder.makeCast(operand.getType(), constant);
+ if (castRight instanceof RexLiteral) {
+ left = operand;
+ constant = clazz.cast(castRight);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ map.put(left, constant);
+ } else {
+ if (existedValue instanceof RexLiteral
+ && constant instanceof RexLiteral
+ && !Objects.equals(
+ ((RexLiteral) existedValue).getValue(),
+ ((RexLiteral) constant).getValue())) {
+ // we found conflicting values, e.g. left = 10 and left = 20
+ map.remove(left);
+ excludeSet.add(left);
+ }
+ }
+ }
+
+ /**
+ * Returns whether a value of {@code type2} can be assigned to a variable of {@code type1}.
+ *
+ * For example:
+ *
+ *
+ * - {@code canAssignFrom(BIGINT, TINYINT)} returns {@code true}
+ *
- {@code canAssignFrom(TINYINT, BIGINT)} returns {@code false}
+ *
- {@code canAssignFrom(BIGINT, VARCHAR)} returns {@code false}
+ *
+ */
+ private static boolean canAssignFrom(
+ RelDataType type1, RelDataType type2, RelDataTypeFactory typeFactory) {
+ final SqlTypeName name1 = type1.getSqlTypeName();
+ final SqlTypeName name2 = type2.getSqlTypeName();
+ final RelDataType type1Final = type1;
+ SqlTypeFamily family =
+ requireNonNull(
+ name1.getFamily(),
+ () ->
+ "SqlTypeFamily is null for type "
+ + type1Final
+ + ", SqlTypeName "
+ + name1);
+ if (family == name2.getFamily()) {
+ switch (family) {
+ case NUMERIC:
+ if (SqlTypeUtil.isExactNumeric(type1) && SqlTypeUtil.isExactNumeric(type2)) {
+ int precision1;
+ int scale1;
+ if (name1 == SqlTypeName.DECIMAL) {
+ type1 = typeFactory.decimalOf(type1);
+ precision1 = type1.getPrecision();
+ scale1 = type1.getScale();
+ } else {
+ precision1 = typeFactory.getTypeSystem().getMaxPrecision(name1);
+ scale1 = typeFactory.getTypeSystem().getMaxScale(name1);
+ }
+ int precision2;
+ int scale2;
+ if (name2 == SqlTypeName.DECIMAL) {
+ type2 = typeFactory.decimalOf(type2);
+ precision2 = type2.getPrecision();
+ scale2 = type2.getScale();
+ } else {
+ precision2 = typeFactory.getTypeSystem().getMaxPrecision(name2);
+ scale2 = typeFactory.getTypeSystem().getMaxScale(name2);
+ }
+ return precision1 >= precision2 && scale1 >= scale2;
+ } else if (SqlTypeUtil.isApproximateNumeric(type1)
+ && SqlTypeUtil.isApproximateNumeric(type2)) {
+ return type1.getPrecision() >= type2.getPrecision()
+ && type1.getScale() >= type2.getScale();
+ }
+ break;
+ default:
+ // getPrecision() will return:
+ // - number of decimal digits for fractional seconds for datetime types
+ // - length in characters for character types
+ // - length in bytes for binary types
+ // - RelDataType.PRECISION_NOT_SPECIFIED (-1) if not applicable for this type
+ return type1.getPrecision() >= type2.getPrecision();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of nodes (including leaves) in a list of expressions.
+ *
+ * @see RexNode#nodeCount()
+ */
+ public static int nodeCount(List extends RexNode> nodes) {
+ return nodeCount(0, nodes);
+ }
+
+ static int nodeCount(int n, List extends RexNode> nodes) {
+ for (RexNode operand : nodes) {
+ n += operand.nodeCount();
+ }
+ return n;
+ }
+
+ /** Returns a visitor that finds nodes of a given {@link SqlKind}. */
+ public static RexFinder find(final SqlKind kind) {
+ return new RexFinder() {
+ @Override
+ public Void visitCall(RexCall call) {
+ if (call.getKind() == kind) {
+ throw Util.FoundOne.NULL;
+ }
+ return super.visitCall(call);
+ }
+ };
+ }
+
+ /** Returns a visitor that finds nodes of given {@link SqlKind}s. */
+ public static RexFinder find(final Set kinds) {
+ return new RexFinder() {
+ @Override
+ public Void visitCall(RexCall call) {
+ if (kinds.contains(call.getKind())) {
+ throw Util.FoundOne.NULL;
+ }
+ return super.visitCall(call);
+ }
+ };
+ }
+
+ /** Returns a visitor that finds a particular {@link RexInputRef}. */
+ public static RexFinder find(final RexInputRef ref) {
+ return new RexFinder() {
+ @Override
+ public Void visitInputRef(RexInputRef inputRef) {
+ if (ref.equals(inputRef)) {
+ throw Util.FoundOne.NULL;
+ }
+ return super.visitInputRef(inputRef);
+ }
+ };
+ }
+
+ /** Expands all the calls to {@link SqlStdOperatorTable#SEARCH} in an expression. */
+ public static RexNode expandSearch(
+ RexBuilder rexBuilder, @Nullable RexProgram program, RexNode node) {
+ return expandSearch(rexBuilder, program, node, -1);
+ }
+
+ /**
+ * Expands calls to {@link SqlStdOperatorTable#SEARCH} whose complexity is greater than {@code
+ * maxComplexity} in an expression.
+ */
+ public static RexNode expandSearch(
+ RexBuilder rexBuilder, @Nullable RexProgram program, RexNode node, int maxComplexity) {
+ return node.accept(searchShuttle(rexBuilder, program, maxComplexity));
+ }
+
+ /**
+ * Creates a shuttle that expands calls to {@link SqlStdOperatorTable#SEARCH}.
+ *
+ * If {@code maxComplexity} is non-negative, a {@link Sarg} whose complexity is greater than
+ * {@code maxComplexity} is retained (not expanded); this gives a means to simplify simple
+ * expressions such as {@code x IS NULL} or {@code x > 10} while keeping more complex
+ * expressions such as {@code x IN (3, 5, 7) OR x IS NULL} as a Sarg.
+ */
+ public static RexShuttle searchShuttle(
+ RexBuilder rexBuilder, @Nullable RexProgram program, int maxComplexity) {
+ return new SearchExpandingShuttle(program, rexBuilder, maxComplexity);
+ }
+
+ @SuppressWarnings("BetaApi")
+ public static > RexNode sargRef(
+ RexBuilder rexBuilder,
+ RexNode ref,
+ Sarg sarg,
+ RelDataType type,
+ RexUnknownAs unknownAs) {
+ if (sarg.isAll() || sarg.isNone()) {
+ return simpleSarg(rexBuilder, ref, sarg, unknownAs);
+ }
+ final List orList = new ArrayList<>();
+ if (sarg.nullAs == RexUnknownAs.TRUE && unknownAs == RexUnknownAs.UNKNOWN) {
+ orList.add(rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, ref));
+ }
+ if (sarg.isPoints()) {
+ // Generate 'ref = value1 OR ... OR ref = valueN'
+ sarg.rangeSet
+ .asRanges()
+ .forEach(
+ range ->
+ orList.add(
+ rexBuilder.makeCall(
+ SqlStdOperatorTable.EQUALS,
+ ref,
+ rexBuilder.makeLiteral(
+ range.lowerEndpoint(),
+ type,
+ true,
+ true))));
+ } else if (sarg.isComplementedPoints()) {
+ // Generate 'ref <> value1 AND ... AND ref <> valueN'
+ final List list =
+ sarg.rangeSet.complement().asRanges().stream()
+ .map(
+ range ->
+ rexBuilder.makeCall(
+ SqlStdOperatorTable.NOT_EQUALS,
+ ref,
+ rexBuilder.makeLiteral(
+ range.lowerEndpoint(),
+ type,
+ true,
+ true)))
+ .collect(Util.toImmutableList());
+ orList.add(composeConjunction(rexBuilder, list));
+ } else {
+ final RangeSets.Consumer consumer = new RangeToRex<>(ref, orList, rexBuilder, type);
+ RangeSets.forEach(sarg.rangeSet, consumer);
+ }
+ RexNode node = composeDisjunction(rexBuilder, orList);
+ if (sarg.nullAs == RexUnknownAs.FALSE && unknownAs == RexUnknownAs.UNKNOWN) {
+ node =
+ rexBuilder.makeCall(
+ SqlStdOperatorTable.AND,
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, ref),
+ node);
+ }
+ return node;
+ }
+
+ /** Expands an 'all' or 'none' sarg. */
+ public static > RexNode simpleSarg(
+ RexBuilder rexBuilder, RexNode ref, Sarg sarg, RexUnknownAs unknownAs) {
+ assert sarg.isAll() || sarg.isNone();
+ final RexUnknownAs nullAs = sarg.nullAs == RexUnknownAs.UNKNOWN ? unknownAs : sarg.nullAs;
+ if (sarg.isAll()) {
+ switch (nullAs) {
+ case TRUE:
+ return rexBuilder.makeLiteral(true);
+ case FALSE:
+ return rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, ref);
+ case UNKNOWN:
+ // "x IS NOT NULL OR UNKNOWN"
+ return rexBuilder.makeCall(
+ SqlStdOperatorTable.OR,
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, ref),
+ rexBuilder.makeNullLiteral(
+ rexBuilder.typeFactory.createSqlType(SqlTypeName.BOOLEAN)));
+ }
+ }
+ if (sarg.isNone()) {
+ switch (nullAs) {
+ case TRUE:
+ return rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, ref);
+ case FALSE:
+ return rexBuilder.makeLiteral(false);
+ case UNKNOWN:
+ // "CASE WHEN x IS NULL THEN UNKNOWN ELSE FALSE END", or "x <> x"
+ return rexBuilder.makeCall(SqlStdOperatorTable.NOT_EQUALS, ref, ref);
+ }
+ }
+ throw new AssertionError();
+ }
+
+ private static RexNode deref(@Nullable RexProgram program, RexNode node) {
+ while (node instanceof RexLocalRef) {
+ node = requireNonNull(program, "program").getExprList().get(((RexLocalRef) node).index);
+ }
+ return node;
+ }
+
+ /** Walks over an expression and determines whether it is constant. */
+ static class ConstantFinder implements RexVisitor {
+ static final ConstantFinder INSTANCE = new ConstantFinder();
+
+ @Override
+ public Boolean visitLiteral(RexLiteral literal) {
+ return true;
+ }
+
+ @Override
+ public Boolean visitInputRef(RexInputRef inputRef) {
+ return false;
+ }
+
+ @Override
+ public Boolean visitLocalRef(RexLocalRef localRef) {
+ return false;
+ }
+
+ @Override
+ public Boolean visitOver(RexOver over) {
+ return false;
+ }
+
+ @Override
+ public Boolean visitSubQuery(RexSubQuery subQuery) {
+ return false;
+ }
+
+ @Override
+ public Boolean visitTableInputRef(RexTableInputRef ref) {
+ return false;
+ }
+
+ @Override
+ public Boolean visitPatternFieldRef(RexPatternFieldRef fieldRef) {
+ return false;
+ }
+
+ @Override
+ public Boolean visitCorrelVariable(RexCorrelVariable correlVariable) {
+ // Correlating variables change when there is an internal restart.
+ // Not good enough for our purposes.
+ return false;
+ }
+
+ @Override
+ public Boolean visitDynamicParam(RexDynamicParam dynamicParam) {
+ // Dynamic parameters are constant WITHIN AN EXECUTION, so that's
+ // good enough.
+ return true;
+ }
+
+ @Override
+ public Boolean visitCall(RexCall call) {
+ // Constant if operator meets the following conditions:
+ // 1. It is deterministic;
+ // 2. All its operands are constant.
+ return call.getOperator().isDeterministic()
+ && RexVisitorImpl.visitArrayAnd(this, call.getOperands());
+ }
+
+ @Override
+ public Boolean visitRangeRef(RexRangeRef rangeRef) {
+ return false;
+ }
+
+ @Override
+ public Boolean visitFieldAccess(RexFieldAccess fieldAccess) {
+ // ".FIELD" is constant iff "" is constant.
+ return fieldAccess.getReferenceExpr().accept(this);
+ }
+ }
+
+ /**
+ * Returns whether node is made up of constants.
+ *
+ * @param node Node to inspect
+ * @return true if node is made up of constants, false otherwise
+ */
+ public static boolean isConstant(RexNode node) {
+ return node.accept(ConstantFinder.INSTANCE);
+ }
+
+ /**
+ * Returns whether a given expression is deterministic.
+ *
+ * @param e Expression
+ * @return true if tree result is deterministic, false otherwise
+ */
+ public static boolean isDeterministic(RexNode e) {
+ try {
+ RexVisitor visitor =
+ new RexVisitorImpl(true) {
+ @Override
+ public Void visitCall(RexCall call) {
+ if (!call.getOperator().isDeterministic()) {
+ throw Util.FoundOne.NULL;
+ }
+ return super.visitCall(call);
+ }
+ };
+ e.accept(visitor);
+ return true;
+ } catch (Util.FoundOne ex) {
+ Util.swallow(ex, null);
+ return false;
+ }
+ }
+
+ public static List retainDeterministic(List list) {
+ List conjuctions = new ArrayList<>();
+ for (RexNode x : list) {
+ if (isDeterministic(x)) {
+ conjuctions.add(x);
+ }
+ }
+ return conjuctions;
+ }
+
+ /**
+ * Returns whether a given node contains a RexCall with a specified operator.
+ *
+ * @param operator Operator to look for
+ * @param node a RexNode tree
+ */
+ public static @Nullable RexCall findOperatorCall(final SqlOperator operator, RexNode node) {
+ try {
+ RexVisitor visitor =
+ new RexVisitorImpl(true) {
+ @Override
+ public Void visitCall(RexCall call) {
+ if (call.getOperator().equals(operator)) {
+ throw new Util.FoundOne(call);
+ }
+ return super.visitCall(call);
+ }
+ };
+ node.accept(visitor);
+ return null;
+ } catch (Util.FoundOne e) {
+ Util.swallow(e, null);
+ return (RexCall) e.getNode();
+ }
+ }
+
+ /**
+ * Returns whether a given tree contains any {link RexInputRef} nodes.
+ *
+ * @param node a RexNode tree
+ */
+ public static boolean containsInputRef(RexNode node) {
+ try {
+ RexVisitor visitor =
+ new RexVisitorImpl(true) {
+ @Override
+ public Void visitInputRef(RexInputRef inputRef) {
+ throw new Util.FoundOne(inputRef);
+ }
+ };
+ node.accept(visitor);
+ return false;
+ } catch (Util.FoundOne e) {
+ Util.swallow(e, null);
+ return true;
+ }
+ }
+
+ /**
+ * Returns whether a given tree contains any {@link org.apache.calcite.rex.RexFieldAccess}
+ * nodes.
+ *
+ * @param node a RexNode tree
+ */
+ public static boolean containsFieldAccess(RexNode node) {
+ try {
+ RexVisitor visitor =
+ new RexVisitorImpl(true) {
+ @Override
+ public Void visitFieldAccess(RexFieldAccess fieldAccess) {
+ throw new Util.FoundOne(fieldAccess);
+ }
+ };
+ node.accept(visitor);
+ return false;
+ } catch (Util.FoundOne e) {
+ Util.swallow(e, null);
+ return true;
+ }
+ }
+
+ /**
+ * Determines whether a {@link RexCall} requires decimal expansion. It usually requires
+ * expansion if it has decimal operands.
+ *
+ * Exceptions to this rule are:
+ *
+ *
+ * - isNull doesn't require expansion
+ *
- It's okay to cast decimals to and from char types
+ *
- It's okay to cast nulls as decimals
+ *
- Casts require expansion if their return type is decimal
+ *
- Reinterpret casts can handle a decimal operand
+ *
+ *
+ * @param expr expression possibly in need of expansion
+ * @param recurse whether to check nested calls
+ * @return whether the expression requires expansion
+ */
+ public static boolean requiresDecimalExpansion(RexNode expr, boolean recurse) {
+ if (!(expr instanceof RexCall)) {
+ return false;
+ }
+ RexCall call = (RexCall) expr;
+
+ boolean localCheck = true;
+ switch (call.getKind()) {
+ case REINTERPRET:
+ case IS_NULL:
+ localCheck = false;
+ break;
+ case CAST:
+ RelDataType lhsType = call.getType();
+ RelDataType rhsType = call.operands.get(0).getType();
+ if (rhsType.getSqlTypeName() == SqlTypeName.NULL) {
+ return false;
+ }
+ if (SqlTypeUtil.inCharFamily(lhsType) || SqlTypeUtil.inCharFamily(rhsType)) {
+ localCheck = false;
+ } else if (SqlTypeUtil.isDecimal(lhsType) && (lhsType != rhsType)) {
+ return true;
+ }
+ break;
+ default:
+ localCheck = call.getOperator().requiresDecimalExpansion();
+ }
+
+ if (localCheck) {
+ if (SqlTypeUtil.isDecimal(call.getType())) {
+ // NOTE jvs 27-Mar-2007: Depending on the type factory, the
+ // result of a division may be decimal, even though both inputs
+ // are integer.
+ return true;
+ }
+ for (int i = 0; i < call.operands.size(); i++) {
+ if (SqlTypeUtil.isDecimal(call.operands.get(i).getType())) {
+ return true;
+ }
+ }
+ }
+ return recurse && requiresDecimalExpansion(call.operands, true);
+ }
+
+ /** Determines whether any operand of a set requires decimal expansion. */
+ public static boolean requiresDecimalExpansion(List operands, boolean recurse) {
+ for (RexNode operand : operands) {
+ if (operand instanceof RexCall) {
+ RexCall call = (RexCall) operand;
+ if (requiresDecimalExpansion(call, recurse)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether a {@link RexProgram} contains expressions which require decimal expansion.
+ */
+ public static boolean requiresDecimalExpansion(RexProgram program, boolean recurse) {
+ final List exprList = program.getExprList();
+ for (RexNode expr : exprList) {
+ if (requiresDecimalExpansion(expr, recurse)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean canReinterpretOverflow(RexCall call) {
+ assert call.isA(SqlKind.REINTERPRET) : "call is not a reinterpret";
+ return call.operands.size() > 1;
+ }
+
+ /** Returns whether an array of expressions has any common sub-expressions. */
+ public static boolean containNoCommonExprs(List exprs, Litmus litmus) {
+ final ExpressionNormalizer visitor = new ExpressionNormalizer(false);
+ for (RexNode expr : exprs) {
+ try {
+ expr.accept(visitor);
+ } catch (ExpressionNormalizer.SubExprExistsException e) {
+ Util.swallow(e, null);
+ return litmus.fail(null);
+ }
+ }
+ return litmus.succeed();
+ }
+
+ /**
+ * Returns whether an array of expressions contains no forward references. That is, if
+ * expression #i contains a {@link RexInputRef} referencing field i or greater.
+ *
+ * @param exprs Array of expressions
+ * @param inputRowType Input row type
+ * @param litmus What to do if an error is detected (there is a forward reference)
+ * @return Whether there is a forward reference
+ */
+ public static boolean containNoForwardRefs(
+ List exprs, RelDataType inputRowType, Litmus litmus) {
+ final ForwardRefFinder visitor = new ForwardRefFinder(inputRowType);
+ for (int i = 0; i < exprs.size(); i++) {
+ RexNode expr = exprs.get(i);
+ visitor.setLimit(i); // field cannot refer to self or later field
+ try {
+ expr.accept(visitor);
+ } catch (ForwardRefFinder.IllegalForwardRefException e) {
+ Util.swallow(e, null);
+ return litmus.fail("illegal forward reference in {}", expr);
+ }
+ }
+ return litmus.succeed();
+ }
+
+ /**
+ * Returns whether an array of exp contains no aggregate function calls whose arguments are not
+ * {@link RexInputRef}s.
+ *
+ * @param exprs Expressions
+ * @param litmus Whether to assert if there is such a function call
+ */
+ static boolean containNoNonTrivialAggs(List exprs, Litmus litmus) {
+ for (RexNode expr : exprs) {
+ if (expr instanceof RexCall) {
+ RexCall rexCall = (RexCall) expr;
+ if (rexCall.getOperator() instanceof SqlAggFunction) {
+ for (RexNode operand : rexCall.operands) {
+ if (!(operand instanceof RexLocalRef) && !(operand instanceof RexLiteral)) {
+ return litmus.fail("contains non trivial agg: {}", operand);
+ }
+ }
+ }
+ }
+ }
+ return litmus.succeed();
+ }
+
+ /**
+ * Returns whether a list of expressions contains complex expressions, that is, a call whose
+ * arguments are not {@link RexVariable} (or a subtype such as {@link RexInputRef}) or {@link
+ * RexLiteral}.
+ */
+ public static boolean containComplexExprs(List exprs) {
+ for (RexNode expr : exprs) {
+ if (expr instanceof RexCall) {
+ for (RexNode operand : ((RexCall) expr).operands) {
+ if (!isAtomic(operand)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether any of the given expression trees contains a {link RexTableInputRef} node.
+ *
+ * @param nodes a list of RexNode trees
+ * @return true if at least one was found, otherwise false
+ */
+ public static boolean containsTableInputRef(List nodes) {
+ for (RexNode e : nodes) {
+ if (containsTableInputRef(e) != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether a given tree contains any {link RexTableInputRef} nodes.
+ *
+ * @param node a RexNode tree
+ * @return first such node found or null if it there is no such node
+ */
+ public static @Nullable RexTableInputRef containsTableInputRef(RexNode node) {
+ try {
+ RexVisitor visitor =
+ new RexVisitorImpl(true) {
+ @Override
+ public Void visitTableInputRef(RexTableInputRef inputRef) {
+ throw new Util.FoundOne(inputRef);
+ }
+ };
+ node.accept(visitor);
+ return null;
+ } catch (Util.FoundOne e) {
+ Util.swallow(e, null);
+ return (RexTableInputRef) e.getNode();
+ }
+ }
+
+ public static boolean isAtomic(RexNode expr) {
+ return (expr instanceof RexLiteral) || (expr instanceof RexVariable);
+ }
+
+ /**
+ * Returns whether a {@link RexNode node} is a {@link RexCall call} to a given {@link
+ * SqlOperator operator}.
+ */
+ public static boolean isCallTo(RexNode expr, SqlOperator op) {
+ return (expr instanceof RexCall) && (((RexCall) expr).getOperator() == op);
+ }
+
+ /**
+ * Creates a record type with anonymous field names.
+ *
+ * @param typeFactory Type factory
+ * @param exprs Expressions
+ * @return Record type
+ */
+ public static RelDataType createStructType(
+ RelDataTypeFactory typeFactory, final List exprs) {
+ return createStructType(typeFactory, exprs, null, null);
+ }
+
+ /**
+ * Creates a record type with specified field names.
+ *
+ * The array of field names may be null, or any of the names within it can be null. We
+ * recommend using explicit names where possible, because it makes it much easier to figure out
+ * the intent of fields when looking at planner output.
+ *
+ * @param typeFactory Type factory
+ * @param exprs Expressions
+ * @param names Field names, may be null, or elements may be null
+ * @param suggester Generates alternative names if {@code names} is not null and its elements
+ * are not unique
+ * @return Record type
+ */
+ public static RelDataType createStructType(
+ RelDataTypeFactory typeFactory,
+ final List extends RexNode> exprs,
+ @Nullable List extends @Nullable String> names,
+ SqlValidatorUtil.Suggester suggester) {
+ if (names != null && suggester != null) {
+ names =
+ SqlValidatorUtil.uniquify(
+ names, suggester, typeFactory.getTypeSystem().isSchemaCaseSensitive());
+ }
+ final RelDataTypeFactory.Builder builder = typeFactory.builder();
+ for (int i = 0; i < exprs.size(); i++) {
+ String name;
+ if (names == null || (name = names.get(i)) == null) {
+ name = "$f" + i;
+ }
+ builder.add(name, exprs.get(i).getType());
+ }
+ return builder.build();
+ }
+
+ @Deprecated // to be removed before 2.0
+ public static RelDataType createStructType(
+ RelDataTypeFactory typeFactory,
+ final List extends RexNode> exprs,
+ List names) {
+ return createStructType(typeFactory, exprs, names, null);
+ }
+
+ /**
+ * Returns whether the type of an array of expressions is compatible with a struct type.
+ *
+ * @param exprs Array of expressions
+ * @param type Type
+ * @param litmus What to do if an error is detected (there is a mismatch)
+ * @return Whether every expression has the same type as the corresponding member of the struct
+ * type
+ * @see RelOptUtil#eq(String, RelDataType, String, RelDataType, org.apache.calcite.util.Litmus)
+ */
+ public static boolean compatibleTypes(List exprs, RelDataType type, Litmus litmus) {
+ final List fields = type.getFieldList();
+ if (exprs.size() != fields.size()) {
+ return litmus.fail("rowtype mismatches expressions");
+ }
+ for (int i = 0; i < fields.size(); i++) {
+ final RelDataType exprType = exprs.get(i).getType();
+ final RelDataType fieldType = fields.get(i).getType();
+ if (!RelOptUtil.eq("type1", exprType, "type2", fieldType, litmus)) {
+ return litmus.fail(null);
+ }
+ }
+ return litmus.succeed();
+ }
+
+ /**
+ * Creates a key for {@link RexNode} which is the same as another key of another RexNode only if
+ * the two have both the same type and textual representation. For example, "10" integer and
+ * "10" bigint result in different keys.
+ */
+ public static Pair makeKey(RexNode expr) {
+ return Pair.of(expr, expr.getType().getFullTypeString());
+ }
+
+ /**
+ * Returns whether the leading edge of a given array of expressions is wholly {@link
+ * RexInputRef} objects with types corresponding to the underlying datatype.
+ */
+ public static boolean containIdentity(
+ List extends RexNode> exprs, RelDataType rowType, Litmus litmus) {
+ final List fields = rowType.getFieldList();
+ if (exprs.size() < fields.size()) {
+ return litmus.fail("exprs/rowType length mismatch");
+ }
+ for (int i = 0; i < fields.size(); i++) {
+ if (!(exprs.get(i) instanceof RexInputRef)) {
+ return litmus.fail("expr[{}] is not a RexInputRef", i);
+ }
+ RexInputRef inputRef = (RexInputRef) exprs.get(i);
+ if (inputRef.getIndex() != i) {
+ return litmus.fail("expr[{}] has ordinal {}", i, inputRef.getIndex());
+ }
+ if (!RelOptUtil.eq(
+ "type1", exprs.get(i).getType(), "type2", fields.get(i).getType(), litmus)) {
+ return litmus.fail(null);
+ }
+ }
+ return litmus.succeed();
+ }
+
+ /** Returns whether a list of expressions projects the incoming fields. */
+ public static boolean isIdentity(List extends RexNode> exps, RelDataType inputRowType) {
+ return inputRowType.getFieldCount() == exps.size()
+ && containIdentity(exps, inputRowType, Litmus.IGNORE);
+ }
+
+ /** As {@link #composeConjunction(RexBuilder, Iterable, boolean)} but never returns null. */
+ public static RexNode composeConjunction(
+ RexBuilder rexBuilder, Iterable extends @Nullable RexNode> nodes) {
+ final RexNode e = composeConjunction(rexBuilder, nodes, false);
+ return requireNonNull(e, "e");
+ }
+
+ /**
+ * Converts a collection of expressions into an AND. If there are zero expressions, returns
+ * TRUE. If there is one expression, returns just that expression. If any of the expressions are
+ * FALSE, returns FALSE. Removes expressions that always evaluate to TRUE. Returns null only if
+ * {@code nullOnEmpty} and expression is TRUE.
+ */
+ public static @Nullable RexNode composeConjunction(
+ RexBuilder rexBuilder,
+ Iterable extends @Nullable RexNode> nodes,
+ boolean nullOnEmpty) {
+ ImmutableList list = flattenAnd(nodes);
+ switch (list.size()) {
+ case 0:
+ return nullOnEmpty ? null : rexBuilder.makeLiteral(true);
+ case 1:
+ return list.get(0);
+ default:
+ if (containsFalse(list)) {
+ return rexBuilder.makeLiteral(false);
+ }
+ return rexBuilder.makeCall(SqlStdOperatorTable.AND, list);
+ }
+ }
+
+ /**
+ * Flattens a list of AND nodes.
+ *
+ * Treats null nodes as literal TRUE (i.e. ignores them).
+ */
+ public static ImmutableList flattenAnd(Iterable extends @Nullable RexNode> nodes) {
+ if (nodes instanceof Collection && ((Collection) nodes).isEmpty()) {
+ // Optimize common case
+ return ImmutableList.of();
+ }
+ final ImmutableList.Builder builder = ImmutableList.builder();
+ final Set set = new HashSet<>(); // to eliminate duplicates
+ for (RexNode node : nodes) {
+ if (node != null) {
+ addAnd(builder, set, node);
+ }
+ }
+ return builder.build();
+ }
+
+ private static void addAnd(
+ ImmutableList.Builder builder, Set digests, RexNode node) {
+ switch (node.getKind()) {
+ case AND:
+ for (RexNode operand : ((RexCall) node).getOperands()) {
+ addAnd(builder, digests, operand);
+ }
+ return;
+ default:
+ if (!node.isAlwaysTrue() && digests.add(node)) {
+ builder.add(node);
+ }
+ }
+ }
+
+ /**
+ * Converts a collection of expressions into an OR. If there are zero expressions, returns
+ * FALSE. If there is one expression, returns just that expression. If any of the expressions
+ * are TRUE, returns TRUE. Removes expressions that always evaluate to FALSE. Flattens
+ * expressions that are ORs.
+ */
+ public static RexNode composeDisjunction(
+ RexBuilder rexBuilder, Iterable extends RexNode> nodes) {
+ final RexNode e = composeDisjunction(rexBuilder, nodes, false);
+ return requireNonNull(e, "e");
+ }
+
+ /**
+ * Converts a collection of expressions into an OR, optionally returning null if the list is
+ * empty.
+ */
+ public static @Nullable RexNode composeDisjunction(
+ RexBuilder rexBuilder, Iterable extends RexNode> nodes, boolean nullOnEmpty) {
+ ImmutableList list = flattenOr(nodes);
+ switch (list.size()) {
+ case 0:
+ return nullOnEmpty ? null : rexBuilder.makeLiteral(false);
+ case 1:
+ return list.get(0);
+ default:
+ if (containsTrue(list)) {
+ return rexBuilder.makeLiteral(true);
+ }
+ return rexBuilder.makeCall(SqlStdOperatorTable.OR, list);
+ }
+ }
+
+ /** Flattens a list of OR nodes. */
+ public static ImmutableList flattenOr(Iterable extends RexNode> nodes) {
+ if (nodes instanceof Collection && ((Collection) nodes).isEmpty()) {
+ // Optimize common case
+ return ImmutableList.of();
+ }
+ final ImmutableList.Builder builder = ImmutableList.builder();
+ final Set set = new HashSet<>(); // to eliminate duplicates
+ for (RexNode node : nodes) {
+ addOr(builder, set, node);
+ }
+ return builder.build();
+ }
+
+ private static void addOr(
+ ImmutableList.Builder builder, Set set, RexNode node) {
+ switch (node.getKind()) {
+ case OR:
+ for (RexNode operand : ((RexCall) node).getOperands()) {
+ addOr(builder, set, operand);
+ }
+ return;
+ default:
+ if (!node.isAlwaysFalse() && set.add(node)) {
+ builder.add(node);
+ }
+ }
+ }
+
+ /**
+ * Applies a mapping to a collation list.
+ *
+ * @param mapping Mapping
+ * @param collationList Collation list
+ * @return collation list with mapping applied to each field
+ */
+ public static List apply(
+ Mappings.TargetMapping mapping, List collationList) {
+ final List