From 9c09821457e392899e2f46551d40e4ac9ccb1f2a Mon Sep 17 00:00:00 2001 From: Takaaki Nakama Date: Sun, 31 May 2026 15:26:36 +0900 Subject: [PATCH] [CALCITE-7566] Support BigQuery-style bare bracket array literals BigQuery accepts array literals written without the ARRAY keyword, e.g. SELECT [1, 2, 3]. Calcite's parser previously rejected this form with `Encountered "[" at line 1, column 8` even under SqlConformanceEnum.BIG_QUERY. Introduce SqlConformance#allowBareBracketArrayLiteral() (true for BIG_QUERY, BABEL, LENIENT) and add a conformance-gated alternative in the JavaCC ArrayConstructor production that consumes `[ ... ]` and emits the same ARRAY_VALUE_CONSTRUCTOR call used by the existing `ARRAY[...]` syntax, so validation, type derivation, and unparse paths need no further change. --- core/src/main/codegen/templates/Parser.jj | 72 +++++++++++-------- .../sql/validate/SqlAbstractConformance.java | 4 ++ .../calcite/sql/validate/SqlConformance.java | 12 ++++ .../sql/validate/SqlConformanceEnum.java | 11 +++ .../validate/SqlDelegatingConformance.java | 4 ++ .../calcite/sql/parser/SqlParserTest.java | 31 ++++++++ 6 files changed, 106 insertions(+), 28 deletions(-) diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj index 2f784b34125b..0ac5f72d4336 100644 --- a/core/src/main/codegen/templates/Parser.jj +++ b/core/src/main/codegen/templates/Parser.jj @@ -5249,28 +5249,55 @@ SqlNode ArrayConstructor() : final String p; } { - { s = span(); } ( + { s = span(); } ( - // nullary array function call: "array()" (Apache Spark) - LOOKAHEAD(2) - { args = SqlNodeList.EMPTY; } + ( + // nullary array function call: "array()" (Apache Spark) + LOOKAHEAD(2) + { args = SqlNodeList.EMPTY; } + | + args = ParenthesizedQueryOrCommaList(ExprContext.ACCEPT_ALL) + ) + { + if (args.size() == 1 && args.get(0).isA(SqlKind.QUERY)) { + // Array query constructor, 'ARRAY (SELECT * FROM t)' + return SqlStdOperatorTable.ARRAY_QUERY.createCall(s.end(this), args.get(0)); + } else { + // Spark ARRAY function, 'ARRAY(1, 2)', + // equivalent to standard 'ARRAY [1, 2]' + return SqlLibraryOperators.ARRAY.createCall(s.end(this), args.getList()); + } + } | - args = ParenthesizedQueryOrCommaList(ExprContext.ACCEPT_ALL) - ) - { - if (args.size() == 1 && args.get(0).isA(SqlKind.QUERY)) { - // Array query constructor, 'ARRAY (SELECT * FROM t)' - return SqlStdOperatorTable.ARRAY_QUERY.createCall(s.end(this), args.get(0)); - } else { - // Spark ARRAY function, 'ARRAY(1, 2)', - // equivalent to standard 'ARRAY [1, 2]' - return SqlLibraryOperators.ARRAY.createCall(s.end(this), args.getList()); + // by enumeration "ARRAY[e0, e1, ..., eN]" + // TODO: do trigraph as well ??( ??) + ( + args = ExpressionCommaList(s, ExprContext.ACCEPT_SUB_QUERY) + | + { args = SqlNodeList.EMPTY; } + ) + + { + return SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR.createCall( + s.end(this), args.getList()); } - } +<#if (parser.includeParsingStringLiteralAsArrayLiteral!default.parser.includeParsingStringLiteralAsArrayLiteral) > + | + p = SimpleStringLiteral() { + try { + return SqlParserUtil.parseArrayLiteral(p); + } catch (SqlParseException ex) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.illegalArrayExpression(p)); + } + } + + ) | - // by enumeration "ARRAY[e0, e1, ..., eN]" - // TODO: do trigraph as well ??( ??) + // BigQuery bare bracket array literal "[e0, e1, ..., eN]" + LOOKAHEAD({ this.conformance.allowBareBracketArrayLiteral() }) + { s = span(); } ( args = ExpressionCommaList(s, ExprContext.ACCEPT_SUB_QUERY) | @@ -5281,17 +5308,6 @@ SqlNode ArrayConstructor() : return SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR.createCall( s.end(this), args.getList()); } -<#if (parser.includeParsingStringLiteralAsArrayLiteral!default.parser.includeParsingStringLiteralAsArrayLiteral) > - | - p = SimpleStringLiteral() { - try { - return SqlParserUtil.parseArrayLiteral(p); - } catch (SqlParseException ex) { - throw SqlUtil.newContextException(getPos(), - RESOURCE.illegalArrayExpression(p)); - } - } - ) } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java index b19d8c071d10..cdc311636b3e 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java @@ -141,6 +141,10 @@ public abstract class SqlAbstractConformance implements SqlConformance { return SqlConformanceEnum.DEFAULT.allowExtendedTrim(); } + @Override public boolean allowBareBracketArrayLiteral() { + return SqlConformanceEnum.DEFAULT.allowBareBracketArrayLiteral(); + } + @Override public boolean allowPluralTimeUnits() { return SqlConformanceEnum.DEFAULT.allowPluralTimeUnits(); } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java index fe902759db16..6141ab41d235 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java @@ -570,6 +570,18 @@ default boolean isColonFieldAccessAllowed() { */ boolean allowExtendedTrim(); + /** + * Whether array literals may omit the {@code ARRAY} keyword, using + * bare square brackets, as in BigQuery, e.g. {@code SELECT [1, 2, 3]}. + * + *

Among the built-in conformance levels, true in + * {@link SqlConformanceEnum#BABEL}, + * {@link SqlConformanceEnum#BIG_QUERY}, + * {@link SqlConformanceEnum#LENIENT}; + * false otherwise. + */ + boolean allowBareBracketArrayLiteral(); + /** * Whether interval literals should allow plural time units * such as "YEARS" and "DAYS" in interval literals. diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java index 047f05981a16..3d6a61ef983c 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java @@ -409,6 +409,17 @@ public enum SqlConformanceEnum implements SqlConformance { } } + @Override public boolean allowBareBracketArrayLiteral() { + switch (this) { + case BABEL: + case BIG_QUERY: + case LENIENT: + return true; + default: + return false; + } + } + @Override public boolean allowPluralTimeUnits() { switch (this) { case BABEL: diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java index 25f8d7e03747..b954425df069 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java @@ -150,6 +150,10 @@ protected SqlDelegatingConformance(SqlConformance delegate) { return delegate.allowExtendedTrim(); } + @Override public boolean allowBareBracketArrayLiteral() { + return delegate.allowBareBracketArrayLiteral(); + } + @Override public boolean allowPluralTimeUnits() { return delegate.allowPluralTimeUnits(); } diff --git a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java index 4ab6f776ba4a..eda1802d75f2 100644 --- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java +++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java @@ -6511,6 +6511,37 @@ private static Matcher isCharLiteral(String s) { expr("array[^select^ 1]").fails("(?s)Encountered \"select\".*"); } + /** Test case for [CALCITE-7566] + * Support BigQuery-style bare bracket array literals. */ + @Test void testBareBracketArrayLiteral() { + final SqlParserFixture bq = + fixture().withConformance(SqlConformanceEnum.BIG_QUERY).expression(); + bq.sql("[1, 2, 3]").ok("(ARRAY[1, 2, 3])"); + // parser allows empty array; validator will reject it + bq.sql("[]").ok("(ARRAY[])"); + // nested bare bracket literals + bq.sql("[[1, 2], [3, 4]]") + .ok("(ARRAY[(ARRAY[1, 2]), (ARRAY[3, 4])])"); + // subscript directly on a bare bracket literal + bq.sql("[10, 20, 30][1]").ok("(ARRAY[10, 20, 30])[1]"); + // SELECT context + fixture().withConformance(SqlConformanceEnum.BIG_QUERY) + .sql("select [1, 2, 3]") + .ok("SELECT (ARRAY[1, 2, 3])"); + + // BABEL and LENIENT also accept the bare bracket syntax + fixture().withConformance(SqlConformanceEnum.BABEL).expression() + .sql("[1, 2, 3]").ok("(ARRAY[1, 2, 3])"); + fixture().withConformance(SqlConformanceEnum.LENIENT).expression() + .sql("[1, 2, 3]").ok("(ARRAY[1, 2, 3])"); + + // Strict conformance levels still reject the bare bracket syntax + expr("^[^1, 2, 3]").fails("(?s).*Encountered \"\\[\".*"); + fixture().withConformance(SqlConformanceEnum.STRICT_2003).expression() + .sql("^[^1, 2, 3]") + .fails("(?s).*Encountered \"\\[\".*"); + } + @Test void testArrayFunction() { expr("array()").ok("ARRAY()"); expr("array(1)").ok("ARRAY(1)");