This is an automated email from the ASF dual-hosted git repository.
fhueske pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git
The following commit(s) were added to refs/heads/master by this push:
new 6fc280fb163 [FLINK-39780] Make TABLE keyword optional in LATERAL
context (#28276)
6fc280fb163 is described below
commit 6fc280fb16372f23f2aa4ab6af3f18dbdda6285a
Author: Fabian Hueske <[email protected]>
AuthorDate: Fri Aug 28 09:59:33 2026 +0200
[FLINK-39780] Make TABLE keyword optional in LATERAL context (#28276)
Extend the SQL parser accept the implicit table-function-call form (e.g.
"FROM t, LATERAL fn(args)") in addition to the explicit "LATERAL
TABLE(fn(args))" form.
Forward ports the Calcite change CALCITE-7183 to Flink.
* Apply CALCITE-7183 Parser.jj change
* extend FlinkSqlParserImplTest
* adjust documentation to favor LATERAL table function calls without TABLE
keyword
Co-Generatedy: Claude Opus 4.8 (1M context)>
---
.../sql-table-concepts/temporal_table_function.md | 2 +-
docs/content.zh/docs/dev/table/functions/udfs.md | 16 +--
.../content.zh/docs/sql/reference/queries/joins.md | 25 ++--
.../docs/sql/reference/queries/overview.md | 3 +-
.../docs/sql/reference/queries/vector-search.md | 16 +--
.../sql-table-concepts/temporal_table_function.md | 2 +-
docs/content/docs/dev/table/functions/udfs.md | 16 +--
docs/content/docs/sql/reference/queries/joins.md | 25 ++--
.../content/docs/sql/reference/queries/overview.md | 3 +-
.../docs/sql/reference/queries/vector-search.md | 16 +--
.../src/main/codegen/templates/Parser.jj | 6 +
.../flink/sql/parser/FlinkSqlParserImplTest.java | 140 +++++++++++++++++++++
12 files changed, 208 insertions(+), 62 deletions(-)
diff --git
a/docs/content.zh/docs/concepts/sql-table-concepts/temporal_table_function.md
b/docs/content.zh/docs/concepts/sql-table-concepts/temporal_table_function.md
index c0ef280ff06..c4d3c405280 100644
---
a/docs/content.zh/docs/concepts/sql-table-concepts/temporal_table_function.md
+++
b/docs/content.zh/docs/concepts/sql-table-concepts/temporal_table_function.md
@@ -112,7 +112,7 @@ SELECT
SUM(amount * rate) AS amount
FROM
orders,
- LATERAL TABLE (rates(order_time))
+ LATERAL rates(order_time)
WHERE
rates.currency = orders.currency
```
diff --git a/docs/content.zh/docs/dev/table/functions/udfs.md
b/docs/content.zh/docs/dev/table/functions/udfs.md
index 273bd8c57c5..79fd1ba19ff 100644
--- a/docs/content.zh/docs/dev/table/functions/udfs.md
+++ b/docs/content.zh/docs/dev/table/functions/udfs.md
@@ -1088,7 +1088,7 @@ env.sqlQuery("SELECT GetBeverageName(beverageId) FROM
Beverages");
在 Table API 中,表值函数是通过 `.joinLateral(...)` 或者 `.leftOuterJoinLateral(...)`
来使用的。`joinLateral` 算子会把外表(算子左侧的表)的每一行跟跟表值函数返回的所有行(位于算子右侧)进行
(cross)join。`leftOuterJoinLateral`
算子也是把外表(算子左侧的表)的每一行跟表值函数返回的所有行(位于算子右侧)进行(cross)join,并且如果表值函数返回 0 行也会保留外表的这一行。
-在 SQL 里面用 `JOIN` 或者 以 `ON TRUE` 为条件的 `LEFT JOIN` 来配合 `LATERAL
TABLE(<TableFunction>)` 的使用。
+在 SQL 里面用 `JOIN` 或者 以 `ON TRUE` 为条件的 `LEFT JOIN` 来配合 `LATERAL
<TableFunction>(...)` 的使用(等价的写法为 `LATERAL TABLE(<TableFunction>(...))`)。
下面的例子展示了如何实现一个分隔函数并在查询里调用它,详情可参考[开发指南](#开发指南):
@@ -1147,17 +1147,17 @@ env
// 在 SQL 里调用注册好的函数
env.sqlQuery(
"SELECT myField, word, length " +
- "FROM MyTable, LATERAL TABLE(SplitFunction(myField))");
+ "FROM MyTable, LATERAL SplitFunction(myField)");
env.sqlQuery(
"SELECT myField, word, length " +
"FROM MyTable " +
- "LEFT JOIN LATERAL TABLE(SplitFunction(myField)) ON TRUE");
+ "LEFT JOIN LATERAL SplitFunction(myField) ON TRUE");
// 在 SQL 里重命名函数字段
env.sqlQuery(
"SELECT myField, newWord, newLength " +
"FROM MyTable " +
- "LEFT JOIN LATERAL TABLE(SplitFunction(myField)) AS T(newWord, newLength) ON
TRUE");
+ "LEFT JOIN LATERAL SplitFunction(myField) AS T(newWord, newLength) ON TRUE");
```
{{< /tab >}}
@@ -1212,17 +1212,17 @@ env
// 在 SQL 里调用注册好的函数
env.sqlQuery(
"SELECT myField, word, length " +
- "FROM MyTable, LATERAL TABLE(SplitFunction(myField))");
+ "FROM MyTable, LATERAL SplitFunction(myField)");
env.sqlQuery(
"SELECT myField, word, length " +
"FROM MyTable " +
- "LEFT JOIN LATERAL TABLE(SplitFunction(myField)) ON TRUE")
+ "LEFT JOIN LATERAL SplitFunction(myField) ON TRUE")
// 在 SQL 里重命名函数字段
env.sqlQuery(
"SELECT myField, newWord, newLength " +
"FROM MyTable " +
- "LEFT JOIN LATERAL TABLE(SplitFunction(myField)) AS T(newWord, newLength) ON
TRUE")
+ "LEFT JOIN LATERAL SplitFunction(myField) AS T(newWord, newLength) ON TRUE")
```
{{< /tab >}}
@@ -1312,7 +1312,7 @@ env.from("MyTable")
.select($("*"))
// 在 SQL 中调用已注册的函数
-env.sqlQuery("SELECT * FROM MyTable, LATERAL
TABLE(BackgroundFunction(myField))");
+env.sqlQuery("SELECT * FROM MyTable, LATERAL BackgroundFunction(myField)");
```
diff --git a/docs/content.zh/docs/sql/reference/queries/joins.md
b/docs/content.zh/docs/sql/reference/queries/joins.md
index a97312e9100..2aad89d7588 100644
--- a/docs/content.zh/docs/sql/reference/queries/joins.md
+++ b/docs/content.zh/docs/sql/reference/queries/joins.md
@@ -259,7 +259,7 @@ SELECT
o_amount, r_rate
FROM
Orders,
- LATERAL TABLE (Rates(o_proctime))
+ LATERAL Rates(o_proctime)
WHERE
r_currency = o_currency
```
@@ -285,7 +285,7 @@ SELECT
o_amount, r_rate
FROM
Orders,
- LATERAL TABLE (Rates(o_proctime))
+ LATERAL Rates(o_proctime)
WHERE
r_currency = o_currency
```
@@ -323,7 +323,7 @@ For example, the following query enriches an append-only
stream of `orders` (the
SELECT o.order_id, o.currency, o.amount, r.rate
FROM orders AS o
-JOIN LATERAL TABLE(SNAPSHOT(input => TABLE currency_rates)) AS r
+JOIN LATERAL SNAPSHOT(input => TABLE currency_rates) AS r
ON o.currency = r.currency;
order_id currency amount rate
@@ -360,19 +360,18 @@ The load phase is what distinguishes a `LATERAL SNAPSHOT`
join from the [process
**Syntax**
-The build side is wrapped in the `SNAPSHOT` table function inside a `LATERAL
TABLE` clause. The outer (probe-side) table must be an append-only table.
+The build side is wrapped in the `SNAPSHOT` table function, which is called
with `LATERAL`. The outer (probe-side) table must be an append-only table.
Both `INNER JOIN` and `LEFT [OUTER] JOIN` are supported. The join requires at
least one conjunctive equality predicate; additional non-equi predicates are
allowed in the `ON` clause.
```sql
SELECT [column_list]
FROM probe_table
-[LEFT] JOIN LATERAL TABLE(
- SNAPSHOT(
- input => TABLE build_table,
- [ load_completed_condition => <'compile_time' | 'user_time'>, ]
- [ load_completed_time => <timestamp_ltz>, ]
- [ load_completed_idle_timeout => <interval>, ]
- [ state_ttl => <interval> ])) AS s
+[LEFT] JOIN LATERAL SNAPSHOT(
+ input => TABLE build_table,
+ [ load_completed_condition => <'compile_time' | 'user_time'>, ]
+ [ load_completed_time => <timestamp_ltz>, ]
+ [ load_completed_idle_timeout => <interval>, ]
+ [ state_ttl => <interval> ]) AS s
ON probe_table.col = s.col
```
@@ -455,7 +454,7 @@ Table Function
```sql
SELECT order_id, res
FROM Orders,
-LATERAL TABLE(table_func(order_id)) t(res)
+LATERAL table_func(order_id) t(res)
```
### LEFT OUTER JOIN
@@ -465,7 +464,7 @@ LATERAL TABLE(table_func(order_id)) t(res)
```sql
SELECT order_id, res
FROM Orders
-LEFT OUTER JOIN LATERAL TABLE(table_func(order_id)) t(res)
+LEFT OUTER JOIN LATERAL table_func(order_id) t(res)
ON TRUE
```
diff --git a/docs/content.zh/docs/sql/reference/queries/overview.md
b/docs/content.zh/docs/sql/reference/queries/overview.md
index 7373700ab52..18aba399c61 100644
--- a/docs/content.zh/docs/sql/reference/queries/overview.md
+++ b/docs/content.zh/docs/sql/reference/queries/overview.md
@@ -316,7 +316,8 @@ tableReference:
tablePrimary:
[ TABLE ] tablePath [ dynamicTableOptions ] [systemTimePeriod] [[AS]
correlationName]
- | LATERAL TABLE '(' functionName '(' expression [, expression ]* ')' ')'
+ | [ LATERAL ] functionName '(' expression [, expression ]* ')'
+ | [ LATERAL ] TABLE '(' functionName '(' expression [, expression ]* ')' ')'
| [ LATERAL ] '(' query ')'
| UNNEST '(' expression ')'
diff --git a/docs/content.zh/docs/sql/reference/queries/vector-search.md
b/docs/content.zh/docs/sql/reference/queries/vector-search.md
index 415004cd5dc..978568932b9 100644
--- a/docs/content.zh/docs/sql/reference/queries/vector-search.md
+++ b/docs/content.zh/docs/sql/reference/queries/vector-search.md
@@ -35,13 +35,13 @@ Flink SQL 提供了 `VECTOR_SEARCH` 表值函数 (TVF) 来在 SQL 查询中执
### 语法
```sql
-SELECT * FROM input_table, LATERAL TABLE(VECTOR_SEARCH(
+SELECT * FROM input_table, LATERAL VECTOR_SEARCH(
TABLE vector_table,
input_table.vector_column,
DESCRIPTOR(index_column),
top_k,
[CONFIG => MAP['key', 'value']]
- ))
+ )
```
### 参数
@@ -64,32 +64,32 @@ SELECT * FROM input_table, LATERAL TABLE(VECTOR_SEARCH(
```sql
-- 基本用法
SELECT * FROM
-input_table, LATERAL TABLE(VECTOR_SEARCH(
+input_table, LATERAL VECTOR_SEARCH(
TABLE vector_table,
input_table.vector_column,
DESCRIPTOR(index_column),
10
-));
+);
-- 带配置选项
SELECT * FROM
-input_table, LATERAL TABLE(VECTOR_SEARCH(
+input_table, LATERAL VECTOR_SEARCH(
TABLE vector_table,
input_table.vector_column,
DESCRIPTOR(index_column),
10,
MAP['async', 'true', 'timeout', '100s']
-));
+);
-- 使用命名参数
SELECT * FROM
-input_table, LATERAL TABLE(VECTOR_SEARCH(
+input_table, LATERAL VECTOR_SEARCH(
SEARCH_TABLE => TABLE vector_table,
COLUMN_TO_QUERY => input_table.vector_column,
COLUMN_TO_SEARCH => DESCRIPTOR(index_column),
TOP_K => 10,
CONFIG => MAP['async', 'true', 'timeout', '100s']
-));
+);
-- 使用常量值搜索
SELECT * FROM TABLE(VECTOR_SEARCH(
diff --git
a/docs/content/docs/concepts/sql-table-concepts/temporal_table_function.md
b/docs/content/docs/concepts/sql-table-concepts/temporal_table_function.md
index 90ae717f988..1f87a57512f 100644
--- a/docs/content/docs/concepts/sql-table-concepts/temporal_table_function.md
+++ b/docs/content/docs/concepts/sql-table-concepts/temporal_table_function.md
@@ -112,7 +112,7 @@ SELECT
SUM(amount * rate) AS amount
FROM
orders,
- LATERAL TABLE (rates(order_time))
+ LATERAL rates(order_time)
WHERE
rates.currency = orders.currency
```
diff --git a/docs/content/docs/dev/table/functions/udfs.md
b/docs/content/docs/dev/table/functions/udfs.md
index 405e0631e49..bd600e16cb3 100644
--- a/docs/content/docs/dev/table/functions/udfs.md
+++ b/docs/content/docs/dev/table/functions/udfs.md
@@ -1130,7 +1130,7 @@ In order to define a table function, one has to extend
the base class `TableFunc
In the Table API, a table function is used with `.joinLateral(...)` or
`.leftOuterJoinLateral(...)`. The `joinLateral` operator (cross) joins each row
from the outer table (table on the left of the operator) with all rows produced
by the table-valued function (which is on the right side of the operator). The
`leftOuterJoinLateral` operator joins each row from the outer table (table on
the left of the operator) with all rows produced by the table-valued function
(which is on the right sid [...]
-In SQL, use `LATERAL TABLE(<TableFunction>)` with `JOIN` or `LEFT JOIN` with
an `ON TRUE` join condition.
+In SQL, use `LATERAL <TableFunction>(...)` (or the equivalent `LATERAL
TABLE(<TableFunction>(...))`) with `JOIN` or `LEFT JOIN` with an `ON TRUE` join
condition.
The following example shows how to define your own split function and call it
in a query. See the [Implementation Guide](#implementation-guide) for more
details.
@@ -1189,17 +1189,17 @@ env
// call registered function in SQL
env.sqlQuery(
"SELECT myField, word, length " +
- "FROM MyTable, LATERAL TABLE(SplitFunction(myField))");
+ "FROM MyTable, LATERAL SplitFunction(myField)");
env.sqlQuery(
"SELECT myField, word, length " +
"FROM MyTable " +
- "LEFT JOIN LATERAL TABLE(SplitFunction(myField)) ON TRUE");
+ "LEFT JOIN LATERAL SplitFunction(myField) ON TRUE");
// rename fields of the function in SQL
env.sqlQuery(
"SELECT myField, newWord, newLength " +
"FROM MyTable " +
- "LEFT JOIN LATERAL TABLE(SplitFunction(myField)) AS T(newWord, newLength) ON
TRUE");
+ "LEFT JOIN LATERAL SplitFunction(myField) AS T(newWord, newLength) ON TRUE");
```
{{< /tab >}}
@@ -1254,17 +1254,17 @@ env
// call registered function in SQL
env.sqlQuery(
"SELECT myField, word, length " +
- "FROM MyTable, LATERAL TABLE(SplitFunction(myField))")
+ "FROM MyTable, LATERAL SplitFunction(myField)")
env.sqlQuery(
"SELECT myField, word, length " +
"FROM MyTable " +
- "LEFT JOIN LATERAL TABLE(SplitFunction(myField)) ON TRUE")
+ "LEFT JOIN LATERAL SplitFunction(myField) ON TRUE")
// rename fields of the function in SQL
env.sqlQuery(
"SELECT myField, newWord, newLength " +
"FROM MyTable " +
- "LEFT JOIN LATERAL TABLE(SplitFunction(myField)) AS T(newWord, newLength) ON
TRUE")
+ "LEFT JOIN LATERAL SplitFunction(myField) AS T(newWord, newLength) ON TRUE")
```
{{< /tab >}}
@@ -1354,7 +1354,7 @@ env.from("MyTable")
.select($("*"))
// call registered function in SQL
-env.sqlQuery("SELECT * FROM MyTable, LATERAL
TABLE(BackgroundFunction(myField))");
+env.sqlQuery("SELECT * FROM MyTable, LATERAL BackgroundFunction(myField)");
```
diff --git a/docs/content/docs/sql/reference/queries/joins.md
b/docs/content/docs/sql/reference/queries/joins.md
index c323554d37b..c5a5123d96a 100644
--- a/docs/content/docs/sql/reference/queries/joins.md
+++ b/docs/content/docs/sql/reference/queries/joins.md
@@ -264,7 +264,7 @@ SELECT
o_amount, r_rate
FROM
Orders,
- LATERAL TABLE (Rates(o_proctime))
+ LATERAL Rates(o_proctime)
WHERE
r_currency = o_currency
```
@@ -290,7 +290,7 @@ SELECT
o_amount, r_rate
FROM
Orders,
- LATERAL TABLE (Rates(o_proctime))
+ LATERAL Rates(o_proctime)
WHERE
r_currency = o_currency
```
@@ -328,7 +328,7 @@ For example, the following query enriches an append-only
stream of `orders` (the
SELECT o.order_id, o.currency, o.amount, r.rate
FROM orders AS o
-JOIN LATERAL TABLE(SNAPSHOT(input => TABLE currency_rates)) AS r
+JOIN LATERAL SNAPSHOT(input => TABLE currency_rates) AS r
ON o.currency = r.currency;
order_id currency amount rate
@@ -369,19 +369,18 @@ By first loading the build side, a `LATERAL SNAPSHOT`
join avoids this problem.
**Syntax**
-The build side is wrapped in the `SNAPSHOT` table function inside a `LATERAL
TABLE` clause. The outer (probe-side) table must be an append-only table.
+The build side is wrapped in the `SNAPSHOT` table function, which is called
with `LATERAL`. The outer (probe-side) table must be an append-only table.
Both `INNER JOIN` and `LEFT [OUTER] JOIN` are supported. The join requires at
least one conjunctive equality predicate; additional non-equi predicates are
allowed in the `ON` clause.
```sql
SELECT [column_list]
FROM probe_table
-[LEFT] JOIN LATERAL TABLE(
- SNAPSHOT(
- input => TABLE build_table,
- [ load_completed_condition => <'compile_time' | 'user_time'>, ]
- [ load_completed_time => <timestamp_ltz>, ]
- [ load_completed_idle_timeout => <interval>, ]
- [ state_ttl => <interval> ])) AS s
+[LEFT] JOIN LATERAL SNAPSHOT(
+ input => TABLE build_table,
+ [ load_completed_condition => <'compile_time' | 'user_time'>, ]
+ [ load_completed_time => <timestamp_ltz>, ]
+ [ load_completed_idle_timeout => <interval>, ]
+ [ state_ttl => <interval> ]) AS s
ON probe_table.col = s.col
```
@@ -546,7 +545,7 @@ The row of the left (outer) table is dropped, if its table
function call returns
```sql
SELECT order_id, res
FROM Orders,
-LATERAL TABLE(table_func(order_id)) t(res)
+LATERAL table_func(order_id) t(res)
```
### LEFT OUTER JOIN
@@ -556,7 +555,7 @@ If a table function call returns an empty result, the
corresponding outer row is
```sql
SELECT order_id, res
FROM Orders
-LEFT OUTER JOIN LATERAL TABLE(table_func(order_id)) t(res)
+LEFT OUTER JOIN LATERAL table_func(order_id) t(res)
ON TRUE
```
diff --git a/docs/content/docs/sql/reference/queries/overview.md
b/docs/content/docs/sql/reference/queries/overview.md
index 1b607d96815..e5346941eab 100644
--- a/docs/content/docs/sql/reference/queries/overview.md
+++ b/docs/content/docs/sql/reference/queries/overview.md
@@ -316,7 +316,8 @@ tableReference:
tablePrimary:
[ TABLE ] tablePath [ dynamicTableOptions ] [systemTimePeriod] [[AS]
correlationName]
- | LATERAL TABLE '(' functionName '(' expression [, expression ]* ')' ')'
+ | [ LATERAL ] functionName '(' expression [, expression ]* ')'
+ | [ LATERAL ] TABLE '(' functionName '(' expression [, expression ]* ')' ')'
| [ LATERAL ] '(' query ')'
| UNNEST '(' expression ')'
diff --git a/docs/content/docs/sql/reference/queries/vector-search.md
b/docs/content/docs/sql/reference/queries/vector-search.md
index 1ffac76125a..4f800ac356f 100644
--- a/docs/content/docs/sql/reference/queries/vector-search.md
+++ b/docs/content/docs/sql/reference/queries/vector-search.md
@@ -37,13 +37,13 @@ The `VECTOR_SEARCH` uses a processing-time attribute to
correlate rows to the la
```sql
SELECT *
-FROM input_table, LATERAL TABLE(VECTOR_SEARCH(
+FROM input_table, LATERAL VECTOR_SEARCH(
TABLE vector_table,
input_table.vector_column,
DESCRIPTOR(index_column),
top_k,
[CONFIG => MAP['key', 'value']]
- ))
+ )
```
### Parameters
@@ -66,32 +66,32 @@ The following configuration options can be specified in the
config map:
```sql
-- Basic usage
SELECT * FROM
-input_table, LATERAL TABLE(VECTOR_SEARCH(
+input_table, LATERAL VECTOR_SEARCH(
TABLE vector_table,
input_table.vector_column,
DESCRIPTOR(index_column),
10
-));
+);
-- With configuration options
SELECT * FROM
-input_table, LATERAL TABLE(VECTOR_SEARCH(
+input_table, LATERAL VECTOR_SEARCH(
TABLE vector_table,
input_table.vector_column,
DESCRIPTOR(index_column),
10,
MAP['async', 'true', 'timeout', '100s']
-));
+);
-- Using named parameters
SELECT * FROM
-input_table, LATERAL TABLE(VECTOR_SEARCH(
+input_table, LATERAL VECTOR_SEARCH(
SEARCH_TABLE => TABLE vector_table,
COLUMN_TO_QUERY => input_table.vector_column,
COLUMN_TO_SEARCH => DESCRIPTOR(index_column),
TOP_K => 10,
CONFIG => MAP['async', 'true', 'timeout', '100s']
-));
+);
-- Searching with contant value
SELECT *
diff --git a/flink-table/flink-sql-parser/src/main/codegen/templates/Parser.jj
b/flink-table/flink-sql-parser/src/main/codegen/templates/Parser.jj
index a031434e8ca..2a23b4982b7 100644
--- a/flink-table/flink-sql-parser/src/main/codegen/templates/Parser.jj
+++ b/flink-table/flink-sql-parser/src/main/codegen/templates/Parser.jj
@@ -2284,6 +2284,12 @@ SqlNode TableRef3(ExprContext exprContext, boolean
lateral) :
{
tableRef = unnestOp.createCall(s.end(this), (List<SqlNode>) args);
}
+ |
+ LOOKAHEAD(<LATERAL> CompoundTableIdentifier() <LPAREN>)
+ <LATERAL> { lateral = true; }
+ tableName = CompoundTableIdentifier()
+ tableRef = ImplicitTableFunctionCallArgs(tableName)
+ tableRef = addLateral(tableRef, lateral)
|
[ <LATERAL> { lateral = true; } ]
tableRef = TableFunctionCall()
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 92f8e34c770..01f8c61541d 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
@@ -4049,6 +4049,146 @@ class FlinkSqlParserImplTest extends SqlParserTest {
.fails("(?s).*Encountered \"\\)\" at .*");
}
+ /**
+ * Overrides {@link
org.apache.calcite.sql.parser.SqlParserTest#testLateral()}: making {@code
+ * TABLE} optional in {@code LATERAL} shifts the error position of the
first (invalid) case.
+ */
+ @Test
+ void testLateral() {
+ // Differs from Calcite: LATERAL <identifier> without an argument list
fails at the
+ // identifier.
+ sql("select * from lateral ^emp^").fails("(?s)Encountered \"emp
<EOF>\" at .*");
+
+ sql("select * from lateral table ^emp^ as e").fails("(?s)Encountered
\"emp\" at .*");
+ sql("select * from lateral table ^scott^.emp").fails("(?s)Encountered
\"scott\" at .*");
+
+ final String expected = "SELECT *\n" + "FROM LATERAL TABLE(`RAMP`(1))";
+
+ // Good: LATERAL TABLE function(arg, arg)
+ sql("select * from lateral table(ramp(1))").ok(expected);
+ sql("select * from lateral table(ramp(1)) as t").ok(expected + " AS
`T`");
+ sql("select * from lateral table(ramp(1)) as t(x)").ok(expected + " AS
`T` (`X`)");
+ // Bad: Parentheses make it look like a sub-query
+ sql("select * from lateral (^table (ramp(1))^)").fails("Expected query
or join");
+
+ // Good: LATERAL (subQuery)
+ final String expected2 = "SELECT *\n" + "FROM LATERAL (SELECT *\n" +
"FROM `EMP`)";
+ sql("select * from lateral (select * from emp)").ok(expected2);
+ sql("select * from lateral (select * from emp) as t").ok(expected2 + "
AS `T`");
+ sql("select * from lateral (select * from emp) as t(x)").ok(expected2
+ " AS `T` (`X`)");
+ }
+
+ /**
+ * Overrides {@link
org.apache.calcite.sql.parser.SqlParserTest#testTemporalTable()} for the
+ * same reason as {@link #testLateral()}: the shifted error position of
the explicit-LATERAL
+ * case.
+ */
+ @Test
+ void testTemporalTable() {
+ final String sql0 =
+ "select stream * from orders, products\n"
+ + "for system_time as of TIMESTAMP '2011-01-02
00:00:00'";
+ final String expected0 =
+ "SELECT STREAM *\n"
+ + "FROM `ORDERS`,\n"
+ + "`PRODUCTS` FOR SYSTEM_TIME AS OF TIMESTAMP
'2011-01-02 00:00:00'";
+ sql(sql0).ok(expected0);
+
+ // Differs from Calcite: explicit LATERAL fails at the identifier (no
argument list).
+ final String sql1 =
+ "select stream * from orders, LATERAL ^products_temporal^\n"
+ + "for system_time as of TIMESTAMP '2011-01-02
00:00:00'";
+ sql(sql1).fails("(?s)Encountered \"products_temporal for\" at line
.*");
+
+ // Inner join with a specific timestamp
+ final String sql2 =
+ "select stream * from orders join products_temporal\n"
+ + "for system_time as of timestamp '2011-01-02
00:00:00'\n"
+ + "on orders.productid = products_temporal.productid";
+ final String expected2 =
+ "SELECT STREAM *\n"
+ + "FROM `ORDERS`\n"
+ + "INNER JOIN `PRODUCTS_TEMPORAL` "
+ + "FOR SYSTEM_TIME AS OF TIMESTAMP '2011-01-02
00:00:00' "
+ + "ON (`ORDERS`.`PRODUCTID` =
`PRODUCTS_TEMPORAL`.`PRODUCTID`)";
+ sql(sql2).ok(expected2);
+
+ // Left join with a timestamp field
+ final String sql3 =
+ "select stream * from orders left join products_temporal\n"
+ + "for system_time as of orders.rowtime "
+ + "on orders.productid = products_temporal.productid";
+ final String expected3 =
+ "SELECT STREAM *\n"
+ + "FROM `ORDERS`\n"
+ + "LEFT JOIN `PRODUCTS_TEMPORAL` "
+ + "FOR SYSTEM_TIME AS OF `ORDERS`.`ROWTIME` "
+ + "ON (`ORDERS`.`PRODUCTID` =
`PRODUCTS_TEMPORAL`.`PRODUCTID`)";
+ sql(sql3).ok(expected3);
+
+ // Left join with a timestamp expression
+ final String sql4 =
+ "select stream * from orders left join products_temporal\n"
+ + "for system_time as of orders.rowtime - INTERVAL '3'
DAY "
+ + "on orders.productid = products_temporal.productid";
+ final String expected4 =
+ "SELECT STREAM *\n"
+ + "FROM `ORDERS`\n"
+ + "LEFT JOIN `PRODUCTS_TEMPORAL` "
+ + "FOR SYSTEM_TIME AS OF (`ORDERS`.`ROWTIME` -
INTERVAL '3' DAY) "
+ + "ON (`ORDERS`.`PRODUCTID` =
`PRODUCTS_TEMPORAL`.`PRODUCTID`)";
+ sql(sql4).ok(expected4);
+ }
+
+ @Test
+ void testLateralImplicitTableFunction() {
+ // Implicit form: LATERAL fn(...) without the TABLE(...) wrapper.
+ sql("select * from t, lateral ramp(t.x)")
+ .ok("SELECT *\n" + "FROM `T`,\n" + "LATERAL
TABLE(`RAMP`(`T`.`X`))");
+
+ // Explicit TABLE wrapper still works.
+ sql("select * from t, lateral table(ramp(t.x))")
+ .ok("SELECT *\n" + "FROM `T`,\n" + "LATERAL
TABLE(`RAMP`(`T`.`X`))");
+
+ // CROSS JOIN form.
+ sql("select * from t cross join lateral ramp(t.x) on true")
+ .ok(
+ "SELECT *\n"
+ + "FROM `T`\n"
+ + "CROSS JOIN LATERAL TABLE(`RAMP`(`T`.`X`))
ON TRUE");
+
+ // LEFT JOIN form with ON condition.
+ sql("select * from t left join lateral ramp(t.x) on t.a = 1")
+ .ok(
+ "SELECT *\n"
+ + "FROM `T`\n"
+ + "LEFT JOIN LATERAL TABLE(`RAMP`(`T`.`X`)) ON
(`T`.`A` = 1)");
+
+ // Named arguments and TABLE-typed arg passed to the function.
+ sql("select * from t, lateral snapshot("
+ + "input => table s, "
+ + "load_completed_condition => 'on_time')")
+ .ok(
+ "SELECT *\n"
+ + "FROM `T`,\n"
+ + "LATERAL TABLE(`SNAPSHOT`("
+ + "`INPUT` => (TABLE `S`), "
+ + "`LOAD_COMPLETED_CONDITION` => 'on_time'))");
+
+ // LATERAL fn(...) as the first FROM entry (no preceding table).
+ sql("select * from lateral ramp(3)").ok("SELECT *\n" + "FROM LATERAL
TABLE(`RAMP`(3))");
+
+ // Documented LATERAL SNAPSHOT join form: JOIN LATERAL fn(named TABLE
arg) AS alias ON ...
+ sql("select o.order_id, r.rate from orders as o "
+ + "join lateral snapshot(input => table
currency_rates) as r "
+ + "on o.currency = r.currency")
+ .ok(
+ "SELECT `O`.`ORDER_ID`, `R`.`RATE`\n"
+ + "FROM `ORDERS` AS `O`\n"
+ + "INNER JOIN LATERAL TABLE(`SNAPSHOT`(`INPUT`
=> (TABLE `CURRENCY_RATES`))) AS `R` "
+ + "ON (`O`.`CURRENCY` = `R`.`CURRENCY`)");
+ }
+
@Test
void testVariantType() {
sql("CREATE TABLE t (\n" + "v variant" + "\n)")