This is an automated email from the ASF dual-hosted git repository.
xuzifu666 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 402d3db818 [CALCITE-5696] Support trailing comma in SELECT list for
Babel parser (like BigQuery)
402d3db818 is described below
commit 402d3db81846ec80d12c8c9f869009b859c360ec
Author: Yu Xu <[email protected]>
AuthorDate: Wed Oct 7 12:21:52 2026 +0800
[CALCITE-5696] Support trailing comma in SELECT list for Babel parser (like
BigQuery)
---
babel/src/main/codegen/config.fmpp | 1 +
babel/src/main/codegen/includes/parserImpls.ftl | 3 +-
.../org/apache/calcite/test/BabelParserTest.java | 67 ++++++++++++++++++++++
.../java/org/apache/calcite/test/BabelTest.java | 18 ++++++
babel/src/test/resources/sql/big-query.iq | 26 +++++++++
babel/src/test/resources/sql/select.iq | 55 ++++++++++++++++++
core/src/main/codegen/default_config.fmpp | 1 +
core/src/main/codegen/templates/Parser.jj | 9 +++
site/_docs/reference.md | 8 +++
9 files changed, 187 insertions(+), 1 deletion(-)
diff --git a/babel/src/main/codegen/config.fmpp
b/babel/src/main/codegen/config.fmpp
index a4e0e9d1ce..eb974fee7b 100644
--- a/babel/src/main/codegen/config.fmpp
+++ b/babel/src/main/codegen/config.fmpp
@@ -619,6 +619,7 @@ data: {
includeIntervalWithoutQualifier: true
includeSelectBy: true
includeDistinctOn: true
+ includeSelectListTrailingComma: true
}
}
diff --git a/babel/src/main/codegen/includes/parserImpls.ftl
b/babel/src/main/codegen/includes/parserImpls.ftl
index 391022d796..467682e750 100644
--- a/babel/src/main/codegen/includes/parserImpls.ftl
+++ b/babel/src/main/codegen/includes/parserImpls.ftl
@@ -121,8 +121,9 @@ SqlNodeList ExtendColumnList() :
<LPAREN> { s = span(); }
ColumnWithType(list)
(
- <COMMA> ColumnWithType(list)
+ LOOKAHEAD(2) <COMMA> ColumnWithType(list)
)*
+ [ <COMMA> ]
<RPAREN> {
return new SqlNodeList(list, s.end(this));
}
diff --git a/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
b/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
index 48172f7d7f..5427f1ee6b 100644
--- a/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
+++ b/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
@@ -185,6 +185,41 @@ class BabelParserTest extends SqlParserTest {
sql(sql2).ok(expected2);
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-5696">[CALCITE-5696]
+ * Support trailing comma in SELECT list for Babel parser (like
BigQuery)</a>.
+ *
+ * <p>GoogleSQL (BigQuery) allows a trailing comma at the end of the SELECT
+ * list, e.g. {@code SELECT a, b, FROM t}; the trailing comma is accepted and
+ * dropped. */
+ @Test void testTrailingCommaInSelectList() {
+ sql("select some_col, from my_table")
+ .ok("SELECT `SOME_COL`\nFROM `MY_TABLE`");
+ sql("select a, b, from t")
+ .ok("SELECT `A`, `B`\nFROM `T`");
+ sql("select 1 as x, from t")
+ .ok("SELECT 1 AS `X`\nFROM `T`");
+
+ // Trailing comma at the end of a FROM-less query
+ sql("select 1,").ok("SELECT 1");
+
+ // Trailing comma inside a subquery
+ sql("select * from (select a, from t)")
+ .ok("SELECT *\nFROM (SELECT `A`\nFROM `T`)");
+
+ // Trailing comma before a set operation
+ sql("select a, from t union select b, from u")
+ .ok("SELECT `A`\nFROM `T`\nUNION\nSELECT `B`\nFROM `U`");
+
+ // Only a single trailing comma is allowed
+ sql("select a, b, ^,^ from t")
+ .fails("(?s)Encountered \",\" at .*");
+
+ // A comma without a preceding item is not allowed
+ sql("select ^,^ from t")
+ .fails("(?s)Encountered \", from\" at .*");
+ }
+
/** Tests that there are no reserved keywords. */
@Disabled
@Test void testKeywords() {
@@ -490,6 +525,38 @@ private void checkParseInfixCast(String sqlType) {
sql(sql).ok(expected);
}
+ @Test void testCreateTableWithTrailingComma() {
+ final String sql = "create table foo (bar integer not null, baz
varchar(30),)";
+ final String expected = "CREATE TABLE `FOO` (`BAR` INTEGER NOT NULL, `BAZ`
VARCHAR(30))";
+ sql(sql).ok(expected);
+
+ final String sql2 = "create table foo (bar integer not null,)";
+ final String expected2 = "CREATE TABLE `FOO` (`BAR` INTEGER NOT NULL)";
+ sql(sql2).ok(expected2);
+
+ // With a table collection type
+ final String sql3 = "create set table foo (bar int not null,)";
+ final String expected3 = "CREATE SET TABLE `FOO` (`BAR` INTEGER NOT NULL)";
+ sql(sql3).ok(expected3);
+
+ // With a query
+ final String sql4 = "create table foo (bar integer,) as select 1";
+ final String expected4 = "CREATE TABLE `FOO` (`BAR` INTEGER) AS\nSELECT 1";
+ sql(sql4).ok(expected4);
+
+ // Only a single trailing comma is allowed
+ sql("create table foo (bar integer,^,^)")
+ .fails("(?s)Encountered \",.*\" at .*");
+
+ // A comma without a preceding column is not allowed
+ sql("create table foo ^(^, bar integer)")
+ .fails("(?s)Encountered \"\\(.*\" at .*");
+
+ // An empty column list is not allowed
+ sql("create table foo ^(^)")
+ .fails("(?s)Encountered \"\\(.*\" at .*");
+ }
+
@Test void testArrayLiteralFromString() {
sql("select array '{1,2,3}'")
.ok("SELECT (ARRAY[1, 2, 3])");
diff --git a/babel/src/test/java/org/apache/calcite/test/BabelTest.java
b/babel/src/test/java/org/apache/calcite/test/BabelTest.java
index 6103d61ed4..7273897f1f 100644
--- a/babel/src/test/java/org/apache/calcite/test/BabelTest.java
+++ b/babel/src/test/java/org/apache/calcite/test/BabelTest.java
@@ -421,6 +421,24 @@ names, is(
"EXPR$0=false\n");
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-5696">[CALCITE-5696]
+ * Support trailing comma in SELECT list for Babel parser (like
BigQuery)</a>.
+ *
+ * <p>Verifies that queries with a trailing comma in the SELECT list are
+ * not only parsed but executed end to end, with the trailing comma dropped.
*/
+ @Test void testTrailingCommaInSelectList() {
+ checkSqlResult("standard",
+ "SELECT x, y, FROM (VALUES (1, 2)) as tbl(x, y)",
+ "X=1; Y=2\n");
+ checkSqlResult("standard",
+ "SELECT 1 AS one, 2 AS two, FROM (VALUES (0)) as tbl(z)",
+ "ONE=1; TWO=2\n");
+ checkSqlResult("standard,bigquery",
+ "SELECT x, y, FROM (VALUES (1, 2)) as tbl(x, y)",
+ "X=1; Y=2\n");
+ }
+
/** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-6030">
* [CALCITE-6030] DATE_PART is not handled by the RexToLixTranslator</a>. */
@Test void testDatePart() {
diff --git a/babel/src/test/resources/sql/big-query.iq
b/babel/src/test/resources/sql/big-query.iq
index a759697954..4b568cfc39 100755
--- a/babel/src/test/resources/sql/big-query.iq
+++ b/babel/src/test/resources/sql/big-query.iq
@@ -104,6 +104,32 @@ from date;
!ok
!}
+# Test case for [CALCITE-5696]
https://issues.apache.org/jira/browse/CALCITE-5696
+# GoogleSQL allows a trailing comma at the end of the SELECT list;
+# the comma is accepted and dropped
+select empno, ename, from emp where deptno = 10 order by empno;
++-------+--------+
+| empno | ename |
++-------+--------+
+| 7782 | CLARK |
+| 7839 | KING |
+| 7934 | MILLER |
++-------+--------+
+(3 rows)
+
+!ok
+
+# Trailing comma in a query without FROM
+select 1 as x, 2 as y,;
++---+---+
+| x | y |
++---+---+
+| 1 | 2 |
++---+---+
+(1 row)
+
+!ok
+
#####################################################################
# DATE, DATETIME, TIME and TIMESTAMP functions ######################
diff --git a/babel/src/test/resources/sql/select.iq
b/babel/src/test/resources/sql/select.iq
index 60af913f14..73e9c5a541 100755
--- a/babel/src/test/resources/sql/select.iq
+++ b/babel/src/test/resources/sql/select.iq
@@ -160,6 +160,61 @@ from emp e join dept d on e.deptno = d.deptno limit 1;
!ok
+# [CALCITE-5696] Trailing comma in SELECT list, as in GoogleSQL (BigQuery);
+# the comma is accepted and the query executes normally
+select empno, ename, from emp where deptno = 10 order by empno;
++-------+--------+
+| EMPNO | ENAME |
++-------+--------+
+| 7782 | CLARK |
+| 7839 | KING |
+| 7934 | MILLER |
++-------+--------+
+(3 rows)
+
+!ok
+
+# Trailing comma at the end of a FROM-less query
+select 1 as one, 2 as two,;
++-----+-----+
+| ONE | TWO |
++-----+-----+
+| 1 | 2 |
++-----+-----+
+(1 row)
+
+!ok
+
+# Trailing comma inside a subquery
+select empno from (select empno, from emp where deptno = 10) order by empno;
++-------+
+| EMPNO |
++-------+
+| 7782 |
+| 7839 |
+| 7934 |
++-------+
+(3 rows)
+
+!ok
+
+# Trailing comma before a set operation
+select ename, from emp where deptno = 10
+union
+select dname, from dept where deptno = 20
+order by ename;
++----------+
+| ENAME |
++----------+
+| CLARK |
+| KING |
+| MILLER |
+| RESEARCH |
++----------+
+(4 rows)
+
+!ok
+
# Test basic BY clause: SELECT a BY b is sugar for SELECT b, a GROUP BY b
ORDER BY b
select ename, empno by deptno from emp;
+--------+--------+-------+
diff --git a/core/src/main/codegen/default_config.fmpp
b/core/src/main/codegen/default_config.fmpp
index 9cb0542337..8c7de50a94 100644
--- a/core/src/main/codegen/default_config.fmpp
+++ b/core/src/main/codegen/default_config.fmpp
@@ -462,4 +462,5 @@ parser: {
includeIntervalWithoutQualifier: false
includeStarExclude: false
includeDistinctOn: false
+ includeSelectListTrailingComma: false
}
diff --git a/core/src/main/codegen/templates/Parser.jj
b/core/src/main/codegen/templates/Parser.jj
index 11c352cf97..726eaf45e9 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -1514,7 +1514,16 @@ SqlSelect SqlSelect() :
keywordList = new SqlNodeList(keywords, s.addAll(keywords).pos());
}
AddSelectItem(selectList)
+<#if
parser.includeSelectListTrailingComma!default.parser.includeSelectListTrailingComma>
+ // GoogleSQL (BigQuery) allows a trailing comma at the end of the SELECT
+ // list, e.g. "SELECT a, b, FROM t". With LOOKAHEAD(2), the loop is exited
+ // when the token after ',' cannot begin a select item; the optional ','
+ // below then consumes the trailing comma.
+ ( LOOKAHEAD(2) <COMMA> AddSelectItem(selectList) )*
+ [ <COMMA> ]
+<#else>
( <COMMA> AddSelectItem(selectList) )*
+</#if>
<#if parser.includeSelectBy!false>
( by = SqlSelectBy() | { by = null; } )
<#else>
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 72a0cd6590..889645ae17 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -232,6 +232,14 @@ ## Grammar
`includeDistinctOn` to `true`. When using `DISTINCT ON`, the expressions in the
`DISTINCT ON` clause must match the beginning of the `ORDER BY` clause.
+A trailing comma at the end of the SELECT list, as in GoogleSQL (BigQuery), is
+accepted for compatibility with programmatically generated queries. This is
+recognized only when the Babel parser is enabled. It sets the generated parser
+configuration flag `includeSelectListTrailingComma` to `true`. The trailing
+comma is not preserved in the parse tree. A trailing comma is likewise accepted
+at the end of the column definition list of a `CREATE TABLE` statement parsed
+by the Babel parser.
+
For example:
{% highlight sql %}