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 %}

Reply via email to