This is an automated email from the ASF dual-hosted git repository.
zhenchen 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 935d676425 [CALCITE-7310] Support the syntax SELECT * EXCLUDE(columns)
935d676425 is described below
commit 935d6764258584b5c30f761042669d3a268bf44f
Author: Zhen Chen <[email protected]>
AuthorDate: Tue Dec 9 06:42:44 2025 +0800
[CALCITE-7310] Support the syntax SELECT * EXCLUDE(columns)
---
babel/src/main/codegen/config.fmpp | 1 +
.../org/apache/calcite/test/BabelParserTest.java | 25 +++++
.../java/org/apache/calcite/test/BabelTest.java | 51 +++++++++
babel/src/test/resources/sql/select.iq | 53 +++++++++
core/src/main/codegen/default_config.fmpp | 1 +
core/src/main/codegen/templates/Parser.jj | 61 +++++++++++
.../apache/calcite/runtime/CalciteResource.java | 6 ++
.../org/apache/calcite/sql/SqlStarExclude.java | 84 +++++++++++++++
.../calcite/sql/validate/SqlValidatorImpl.java | 118 +++++++++++++++++++--
.../calcite/runtime/CalciteResource.properties | 2 +
site/_docs/reference.md | 12 ++-
11 files changed, 406 insertions(+), 8 deletions(-)
diff --git a/babel/src/main/codegen/config.fmpp
b/babel/src/main/codegen/config.fmpp
index c41f28bd71..b9c4a1c6ee 100644
--- a/babel/src/main/codegen/config.fmpp
+++ b/babel/src/main/codegen/config.fmpp
@@ -617,6 +617,7 @@ data: {
includePosixOperators: true
includeParsingStringLiteralAsArrayLiteral: true
includeIntervalWithoutQualifier: true
+ includeStarExclude: true
}
}
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 a0fb721aef..e3d49d2bb6 100644
--- a/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
+++ b/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
@@ -136,6 +136,31 @@ class BabelParserTest extends SqlParserTest {
+ "FROM \"t\"");
}
+ /** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-7310">
+ * [CALCITE-7310] Support the syntax SELECT * EXCLUDE(columns)</a>. */
+ @Test void testStarExclude() {
+ final String sql = "select * exclude(empno) from emp";
+ final String expected = "SELECT * EXCLUDE (`EMPNO`)\n"
+ + "FROM `EMP`";
+ sql(sql).ok(expected);
+
+ final String sql2 = "select e.* exclude(e.empno, e.ename, e.job, e.mgr,
d.deptno)"
+ + " from emp e join dept d on e.deptno = d.deptno";
+ final String expected2 = "SELECT `E`.* EXCLUDE (`E`.`EMPNO`, `E`.`ENAME`,"
+ + " `E`.`JOB`, `E`.`MGR`, `D`.`DEPTNO`)\n"
+ + "FROM `EMP` AS `E`\n"
+ + "INNER JOIN `DEPT` AS `D` ON (`E`.`DEPTNO` = `D`.`DEPTNO`)";
+ sql(sql2).ok(expected2);
+
+ final String sql3 = "select e.* exclude(e.empno, e.ename, e.job, e.mgr,
d.deptno),"
+ + " d.* exclude(d.dname) from emp e join dept d on e.deptno =
d.deptno";
+ final String expected3 = "SELECT `E`.* EXCLUDE (`E`.`EMPNO`, `E`.`ENAME`,"
+ + " `E`.`JOB`, `E`.`MGR`, `D`.`DEPTNO`), `D`.* EXCLUDE (`D`.`DNAME`)\n"
+ + "FROM `EMP` AS `E`\n"
+ + "INNER JOIN `DEPT` AS `D` ON (`E`.`DEPTNO` = `D`.`DEPTNO`)";
+ sql(sql3).ok(expected3);
+ }
+
/** Tests that there are no reserved keywords. */
@Disabled
@Test void testKeywords() {
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 1feb2e6020..9368a7ab71 100644
--- a/babel/src/test/java/org/apache/calcite/test/BabelTest.java
+++ b/babel/src/test/java/org/apache/calcite/test/BabelTest.java
@@ -18,6 +18,7 @@
import org.apache.calcite.config.CalciteConnectionProperty;
import org.apache.calcite.rel.type.DelegatingTypeSystem;
+import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rel.type.TimeFrameSet;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.fun.SqlLibrary;
@@ -26,6 +27,8 @@
import org.apache.calcite.sql.parser.babel.SqlBabelParserImpl;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
+import com.google.common.collect.ImmutableList;
+
import org.junit.jupiter.api.Test;
import java.sql.Connection;
@@ -35,8 +38,10 @@
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
+import java.util.List;
import java.util.Properties;
import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -159,6 +164,52 @@ private void checkInfixCast(Statement statement, String
typeName, int sqlType)
.fails("(?s).*Encountered \":\" at .*");
}
+ /** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-7310">
+ * [CALCITE-7310] Support the syntax SELECT * EXCLUDE(columns)</a>. */
+ @Test void testStarExcludeValidation() {
+ final SqlValidatorFixture fixture = Fixtures.forValidator()
+ .withParserConfig(p ->
p.withParserFactory(SqlBabelParserImpl.FACTORY));
+
+ fixture.withSql("select * exclude(empno, deptno) from emp")
+ .type(type -> {
+ final List<String> names = type.getFieldList().stream()
+ .map(RelDataTypeField::getName)
+ .collect(Collectors.toList());
+ assertThat(
+ names, is(
+ ImmutableList.of("ENAME", "JOB", "MGR", "HIREDATE", "SAL",
"COMM", "SLACKER")));
+ });
+
+ fixture.withSql("select * exclude (empno, ^foo^) from emp")
+ .fails("SELECT \\* EXCLUDE list contains unknown column\\(s\\): FOO");
+
+ fixture.withSql("select e.* exclude(e.empno, e.ename, e.job, e.mgr)"
+ + " from emp e join dept d on e.deptno = d.deptno")
+ .type(type -> {
+ final List<String> names = type.getFieldList().stream()
+ .map(RelDataTypeField::getName)
+ .collect(Collectors.toList());
+ assertThat(
+ names, is(
+ ImmutableList.of("HIREDATE", "SAL", "COMM", "DEPTNO",
"SLACKER")));
+ });
+
+ fixture.withSql("select e.* exclude(e.empno, e.ename, e.job, e.mgr,
^d.deptno^)"
+ + " from emp e join dept d on e.deptno = d.deptno")
+ .fails("SELECT \\* EXCLUDE list contains unknown column\\(s\\):
D.DEPTNO");
+
+ fixture.withSql("select e.* exclude(e.empno, e.ename, e.job, e.mgr), d.*
exclude(d.name)"
+ + " from emp e join dept d on e.deptno = d.deptno")
+ .type(type -> {
+ final List<String> names = type.getFieldList().stream()
+ .map(RelDataTypeField::getName)
+ .collect(Collectors.toList());
+ assertThat(
+ names, is(
+ ImmutableList.of("HIREDATE", "SAL", "COMM", "DEPTNO",
"SLACKER", "DEPTNO0")));
+ });
+ }
+
/** Tests that DATEADD, DATEDIFF, DATEPART, DATE_PART allow custom time
* frames. */
@Test void testTimeFrames() {
diff --git a/babel/src/test/resources/sql/select.iq
b/babel/src/test/resources/sql/select.iq
index 9c234d1a04..6b02cca8e3 100755
--- a/babel/src/test/resources/sql/select.iq
+++ b/babel/src/test/resources/sql/select.iq
@@ -107,4 +107,57 @@ select 1.0 % 2;
!ok
+# [CALCITE-7310] Support the syntax SELECT * EXCLUDE(columns)
+select * exclude(empno, ename, job, mgr) from emp limit 1;
++------------+--------+------+--------+
+| HIREDATE | SAL | COMM | DEPTNO |
++------------+--------+------+--------+
+| 1980-12-17 | 800.00 | | 20 |
++------------+--------+------+--------+
+(1 row)
+
+!ok
+
+select * exclude(empno, ename, job, mgr, mgr) from emp limit 1;
++------------+--------+------+--------+
+| HIREDATE | SAL | COMM | DEPTNO |
++------------+--------+------+--------+
+| 1980-12-17 | 800.00 | | 20 |
++------------+--------+------+--------+
+(1 row)
+
+!ok
+
+select e.*, d.* from emp e join dept d on e.deptno = d.deptno limit 1;
++-------+-------+---------+------+------------+---------+------+--------+---------+------------+----------+
+| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
DEPTNO0 | DNAME | LOC |
++-------+-------+---------+------+------------+---------+------+--------+---------+------------+----------+
+| 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | | 10 |
10 | ACCOUNTING | NEW YORK |
++-------+-------+---------+------+------------+---------+------+--------+---------+------------+----------+
+(1 row)
+
+!ok
+
+select e.* exclude(e.empno, e.ename, e.job, e.mgr)
+from emp e join dept d on e.deptno = d.deptno limit 1;
++------------+--------+------+--------+
+| HIREDATE | SAL | COMM | DEPTNO |
++------------+--------+------+--------+
+| 1980-12-17 | 800.00 | | 20 |
++------------+--------+------+--------+
+(1 row)
+
+!ok
+
+select e.* exclude(e.empno, e.ename, e.job, e.mgr), d.* exclude(d.dname)
+from emp e join dept d on e.deptno = d.deptno limit 1;
++------------+---------+------+--------+---------+----------+
+| HIREDATE | SAL | COMM | DEPTNO | DEPTNO0 | LOC |
++------------+---------+------+--------+---------+----------+
+| 1981-06-09 | 2450.00 | | 10 | 10 | NEW YORK |
++------------+---------+------+--------+---------+----------+
+(1 row)
+
+!ok
+
# End select.iq
diff --git a/core/src/main/codegen/default_config.fmpp
b/core/src/main/codegen/default_config.fmpp
index a2547273cb..56d17b8279 100644
--- a/core/src/main/codegen/default_config.fmpp
+++ b/core/src/main/codegen/default_config.fmpp
@@ -460,4 +460,5 @@ parser: {
includeAdditionalDeclarations: false
includeParsingStringLiteralAsArrayLiteral: false
includeIntervalWithoutQualifier: false
+ includeStarExclude: false
}
diff --git a/core/src/main/codegen/templates/Parser.jj
b/core/src/main/codegen/templates/Parser.jj
index 95d34d4eb6..a6f50f1f12 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -91,6 +91,7 @@ import org.apache.calcite.sql.SqlRowTypeNameSpec;
import org.apache.calcite.sql.SqlSampleSpec;
import org.apache.calcite.sql.SqlSelect;
import org.apache.calcite.sql.SqlSelectKeyword;
+import org.apache.calcite.sql.SqlStarExclude;
import org.apache.calcite.sql.SqlSetOption;
import org.apache.calcite.sql.SqlSnapshot;
import org.apache.calcite.sql.SqlTableRef;
@@ -1974,6 +1975,65 @@ void AddSelectItem(List<SqlNode> list) :
)
}
+<#if (parser.includeStarExclude!default.parser.includeStarExclude)>
+/**
+ * Parses one unaliased expression in a select list.
+ */
+SqlNode SelectExpression() :
+{
+ SqlNode e;
+ SqlNodeList excludeList;
+}
+{
+ (
+ <STAR> {
+ e = SqlIdentifier.star(getPos());
+ }
+ |
+ e = Expression(ExprContext.ACCEPT_SUB_QUERY)
+ )
+ (
+ excludeList = StarExcludeList() {
+ if (!(e instanceof SqlIdentifier)) {
+ throw
SqlUtil.newContextException(excludeList.getParserPosition(),
+ RESOURCE.selectExcludeRequiresStar());
+ }
+ final SqlIdentifier sqlIdentifier = (SqlIdentifier) e;
+ if (!sqlIdentifier.isStar()) {
+ throw
SqlUtil.newContextException(excludeList.getParserPosition(),
+ RESOURCE.selectExcludeRequiresStar());
+ }
+ final SqlParserPos pos = SqlParserPos.sum(
+ ImmutableList.of(sqlIdentifier.getParserPosition(),
+ excludeList.getParserPosition()));
+ return new SqlStarExclude(pos, sqlIdentifier, excludeList);
+ }
+ |
+ { return e; }
+ )
+}
+
+SqlNodeList StarExcludeList() :
+{
+ final Span s;
+ final List<SqlNode> list = new ArrayList<SqlNode>();
+ SqlIdentifier id;
+}
+{
+ <EXCLUDE> <LPAREN> { s = span(); }
+ id = CompoundIdentifier() {
+ list.add(id);
+ }
+ (
+ <COMMA> id = CompoundIdentifier() {
+ list.add(id);
+ }
+ )*
+ <RPAREN> {
+ return new SqlNodeList(list, s.end(this));
+ }
+}
+<#else>
/**
* Parses one unaliased expression in a select list.
*/
@@ -1990,6 +2050,7 @@ SqlNode SelectExpression() :
return e;
}
}
+</#if>
SqlLiteral Natural() :
{
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index 809bdcce70..d35f1a030e 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -807,6 +807,12 @@ ExInst<CalciteException>
illegalArgumentForTableFunctionCall(String a0,
@BaseMessage("SELECT * requires a FROM clause")
ExInst<SqlValidatorException> selectStarRequiresFrom();
+ @BaseMessage("EXCLUDE clause must follow a STAR expression")
+ ExInst<CalciteException> selectExcludeRequiresStar();
+
+ @BaseMessage("SELECT * EXCLUDE list contains unknown column(s): {0}")
+ ExInst<SqlValidatorException>
selectStarExcludeListContainsUnknownColumns(String columns);
+
@BaseMessage("Group function ''{0}'' can only appear in GROUP BY clause")
ExInst<SqlValidatorException> groupFunctionMustAppearInGroupByClause(String
funcName);
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlStarExclude.java
b/core/src/main/java/org/apache/calcite/sql/SqlStarExclude.java
new file mode 100644
index 0000000000..884c4a8463
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql/SqlStarExclude.java
@@ -0,0 +1,84 @@
+/*
+ * 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.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents {@code SELECT * EXCLUDE(...)}.
+ */
+public class SqlStarExclude extends SqlCall {
+ public static final SqlOperator OPERATOR =
+ new SqlSpecialOperator("SELECT_STAR_EXCLUDE", SqlKind.OTHER) {
+ @SuppressWarnings("argument.type.incompatible")
+ @Override public SqlCall createCall(
+ @Nullable SqlLiteral functionQualifier,
+ SqlParserPos pos,
+ @Nullable SqlNode... operands) {
+ return new SqlStarExclude(
+ pos,
+ (SqlIdentifier) operands[0],
+ (SqlNodeList) operands[1]);
+ }
+ };
+
+ private final SqlIdentifier starIdentifier;
+ private final SqlNodeList excludeList;
+
+ public SqlStarExclude(SqlParserPos pos, SqlIdentifier starIdentifier,
+ SqlNodeList excludeList) {
+ super(pos);
+ this.starIdentifier = requireNonNull(starIdentifier, "starIdentifier");
+ this.excludeList = requireNonNull(excludeList, "excludeList");
+ }
+
+ public SqlIdentifier getStarIdentifier() {
+ return starIdentifier;
+ }
+
+ public SqlNodeList getExcludeList() {
+ return excludeList;
+ }
+
+ @Override public SqlOperator getOperator() {
+ return OPERATOR;
+ }
+
+ @Override public SqlKind getKind() {
+ return OPERATOR.getKind();
+ }
+
+ @Override public List<SqlNode> getOperandList() {
+ return ImmutableList.of(starIdentifier, excludeList);
+ }
+
+ @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec)
{
+ starIdentifier.unparse(writer, leftPrec, rightPrec);
+ writer.sep("EXCLUDE");
+ final SqlWriter.Frame frame = writer.startList("(", ")");
+ excludeList.unparse(writer, 0, 0);
+ writer.endList(frame);
+ }
+}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index 96d46174d4..172921babf 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -79,6 +79,7 @@
import org.apache.calcite.sql.SqlSelect;
import org.apache.calcite.sql.SqlSelectKeyword;
import org.apache.calcite.sql.SqlSnapshot;
+import org.apache.calcite.sql.SqlStarExclude;
import org.apache.calcite.sql.SqlSyntax;
import org.apache.calcite.sql.SqlTableFunction;
import org.apache.calcite.sql.SqlUnknownLiteral;
@@ -639,13 +640,26 @@ private static void validateQualifiedCommonColumn(SqlJoin
join,
private boolean expandStar(List<SqlNode> selectItems, Set<String> aliases,
PairList<String, RelDataType> fields, boolean includeSystemVars,
SelectScope scope, SqlNode node) {
- if (!(node instanceof SqlIdentifier)) {
+ final SqlIdentifier identifier;
+ final SqlNodeList excludeList;
+ if (node instanceof SqlStarExclude) {
+ final SqlStarExclude starExclude = (SqlStarExclude) node;
+ identifier = starExclude.getStarIdentifier();
+ excludeList = starExclude.getExcludeList();
+ } else if (node instanceof SqlIdentifier) {
+ identifier = (SqlIdentifier) node;
+ excludeList = null;
+ } else {
return false;
}
- final SqlIdentifier identifier = (SqlIdentifier) node;
if (!identifier.isStar()) {
return false;
}
+ final List<SqlIdentifier> excludeIdentifiers =
+ excludeList == null ? Collections.emptyList() :
extractExcludeIdentifiers(excludeList);
+ final boolean[] excludeMatched = new boolean[excludeIdentifiers.size()];
+ final SqlNameMatcher nameMatcher =
+ scope.validator.catalogReader.nameMatcher();
final int originalSize = selectItems.size();
final SqlParserPos startPosition = identifier.getParserPosition();
switch (identifier.names.size()) {
@@ -687,6 +701,10 @@ private boolean expandStar(List<SqlNode> selectItems,
Set<String> aliases,
new SqlIdentifier(
ImmutableList.of(child.name, columnName),
startPosition);
+ recordExcludeMatches(excludeIdentifiers, exp, nameMatcher,
excludeMatched);
+ if (shouldExcludeField(excludeList, exp, nameMatcher)) {
+ continue;
+ }
// Don't add expanded rolled up columns
if (!isRolledUpColumn(exp, scope)) {
addOrExpandField(
@@ -720,15 +738,16 @@ private boolean expandStar(List<SqlNode> selectItems,
Set<String> aliases,
int offset = Math.min(calculatePermuteOffset(selectItems),
originalSize);
new Permute(from, offset).permute(selectItems, fields);
}
+ throwIfUnknownExcludeColumns(excludeIdentifiers, excludeMatched);
return true;
default:
final SqlIdentifier prefixId = identifier.skipLast(1);
final SqlValidatorScope.ResolvedImpl resolved =
new SqlValidatorScope.ResolvedImpl();
- final SqlNameMatcher nameMatcher =
+ final SqlNameMatcher resolvedNameMatcher =
scope.validator.catalogReader.nameMatcher();
- scope.resolve(prefixId.names, nameMatcher, true, resolved);
+ scope.resolve(prefixId.names, resolvedNameMatcher, true, resolved);
if (resolved.count() == 0) {
// e.g. "select s.t.* from e"
// or "select r.* from e"
@@ -749,6 +768,13 @@ private boolean expandStar(List<SqlNode> selectItems,
Set<String> aliases,
for (RelDataTypeField field : rowType.getFieldList()) {
String columnName = field.getName();
+ final SqlIdentifier columnId =
+ prefixId.plus(columnName, startPosition);
+ recordExcludeMatches(excludeIdentifiers, columnId,
resolvedNameMatcher,
+ excludeMatched);
+ if (shouldExcludeField(excludeList, columnId, resolvedNameMatcher)) {
+ continue;
+ }
// TODO: do real implicit collation here
addOrExpandField(
selectItems,
@@ -756,12 +782,13 @@ private boolean expandStar(List<SqlNode> selectItems,
Set<String> aliases,
fields,
includeSystemVars,
scope,
- prefixId.plus(columnName, startPosition),
+ columnId,
field);
}
} else {
throw newValidationError(prefixId, RESOURCE.starRequiresRecordType());
}
+ throwIfUnknownExcludeColumns(excludeIdentifiers, excludeMatched);
return true;
}
}
@@ -778,6 +805,86 @@ private static int calculatePermuteOffset(List<SqlNode>
selectItems) {
return 0;
}
+ private static boolean matchesExcludeNames(List<String> identifierNames,
+ List<String> excludedIdentifierNames, SqlNameMatcher nameMatcher) {
+ if (excludedIdentifierNames.size() > identifierNames.size()) {
+ return false;
+ }
+ final int offset = identifierNames.size() - excludedIdentifierNames.size();
+ for (int i = 0; i < excludedIdentifierNames.size(); i++) {
+ if (!nameMatcher.matches(identifierNames.get(offset + i),
+ excludedIdentifierNames.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean shouldExcludeField(@Nullable SqlNodeList excludeList,
+ SqlIdentifier columnId, SqlNameMatcher nameMatcher) {
+ if (excludeList == null) {
+ return false;
+ }
+ for (SqlNode node : excludeList) {
+ assert node instanceof SqlIdentifier;
+ if (matchesExcludeIdentifier(columnId, (SqlIdentifier) node,
nameMatcher)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean matchesExcludeIdentifier(SqlIdentifier columnId,
+ SqlIdentifier excludeIdentifier, SqlNameMatcher nameMatcher) {
+ return matchesExcludeNames(columnId.names, excludeIdentifier.names,
nameMatcher);
+ }
+
+ private static List<SqlIdentifier> extractExcludeIdentifiers(@Nullable
SqlNodeList excludeList) {
+ if (excludeList == null) {
+ return ImmutableList.of();
+ }
+ final ImmutableList.Builder<SqlIdentifier> builder =
ImmutableList.builder();
+ for (SqlNode node : excludeList) {
+ if (node instanceof SqlIdentifier) {
+ builder.add((SqlIdentifier) node);
+ }
+ }
+ return builder.build();
+ }
+
+ private static void recordExcludeMatches(List<SqlIdentifier>
excludeIdentifiers,
+ SqlIdentifier columnId, SqlNameMatcher nameMatcher, boolean[] matched) {
+ for (int i = 0; i < excludeIdentifiers.size(); i++) {
+ if (!matched[i]
+ && matchesExcludeIdentifier(columnId, excludeIdentifiers.get(i),
nameMatcher)) {
+ matched[i] = true;
+ }
+ }
+ }
+
+ private void throwIfUnknownExcludeColumns(List<SqlIdentifier>
excludeIdentifiers,
+ boolean[] excludeMatched) {
+ if (excludeIdentifiers.isEmpty()) {
+ return;
+ }
+ final List<String> unknownExcludeNames = new ArrayList<>();
+ int firstUnknownIndex = -1;
+ for (int i = 0; i < excludeIdentifiers.size(); i++) {
+ if (!excludeMatched[i]) {
+ if (firstUnknownIndex < 0) {
+ firstUnknownIndex = i;
+ }
+ unknownExcludeNames.add(excludeIdentifiers.get(i).toString());
+ }
+ }
+ if (firstUnknownIndex >= 0) {
+ throw newValidationError(
+ excludeIdentifiers.get(firstUnknownIndex),
+ RESOURCE.selectStarExcludeListContainsUnknownColumns(
+ String.join(", ", unknownExcludeNames)));
+ }
+ }
+
private SqlNode maybeCast(SqlNode node, RelDataType currentType,
RelDataType desiredType) {
return SqlTypeUtil.equalSansNullability(typeFactory, currentType,
desiredType)
@@ -801,7 +908,6 @@ private boolean addOrExpandField(List<SqlNode> selectItems,
Set<String> aliases,
scope,
starExp);
return true;
-
default:
addToSelectList(
selectItems,
diff --git
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 124e133a14..4cc492795e 100644
---
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -266,6 +266,8 @@ CannotStreamResultsForNonStreamingInputs=Cannot stream
results of a query with n
MinusNotAllowed=MINUS is not allowed under the current SQL conformance level
SelectMissingFrom=SELECT must have a FROM clause
SelectStarRequiresFrom=SELECT * requires a FROM clause
+SelectExcludeRequiresStar=EXCLUDE clause must follow a STAR expression
+SelectStarExcludeListContainsUnknownColumns=SELECT * EXCLUDE list contains
unknown column(s): {0}
GroupFunctionMustAppearInGroupByClause=Group function ''{0}'' can only appear
in GROUP BY clause
AuxiliaryWithoutMatchingGroupCall=Call to auxiliary group function ''{0}''
must have matching call to group function ''{1}'' in GROUP BY clause
PivotAggMalformed=Measure expression in PIVOT must use aggregate function
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index b07ba0c8fa..7017d4565d 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -205,8 +205,8 @@ ## Grammar
expression [ ASC | DESC ] [ NULLS FIRST | NULLS LAST ]
select:
- SELECT [ hintComment ] [ STREAM ] [ ALL | DISTINCT ]
- { * | projectItem [, projectItem ]* }
+ SELECT [ hintComment ] [ STREAM ] [ ALL | DISTINCT ]
+ { starWithExclude | projectItem [, projectItem ]* }
FROM tableExpression
[ WHERE booleanExpression ]
[ GROUP BY [ ALL | DISTINCT ] { groupItem [, groupItem ]* } ]
@@ -218,6 +218,14 @@ ## Grammar
SELECT [ ALL | DISTINCT ]
{ * | projectItem [, projectItem ]* }
+starWithExclude:
+ *
+ | * EXCLUDE '(' column [, column ]* ')'
+
+Note:
+
+* `SELECT * EXCLUDE (...)` is recognized only when the Babel parser is
enabled. It sets the generated parser configuration flag `includeStarExclude`
to `true` (the standard parser leaves that flag `false`), which allows a `STAR`
token followed by `EXCLUDE` and a parenthesized identifier list to be parsed
into a `SqlStarExclude` node and ensures validators respect the exclusion list
when expanding the projection. Reusing the same parser configuration elsewhere
enables the same syntax for [...]
+
projectItem:
expression [ [ AS ] columnAlias ]
| tableAlias . *