This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch site
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/site by this push:
new 4cdf6794ef [CALCITE-7301] Add common test for SqlNode unparse after
deep copy and missing createCall in operators
4cdf6794ef is described below
commit 4cdf6794efd6d525945cee9782ce2ce63f2a5c6e
Author: Dmitry Sysolyatin <[email protected]>
AuthorDate: Wed Nov 19 14:25:12 2025 +0200
[CALCITE-7301] Add common test for SqlNode unparse after deep copy and
missing createCall in operators
---
.../calcite/sql/babel/SqlBabelCreateTable.java | 36 ++++++++++++++++++++--
.../org/apache/calcite/test/BabelParserTest.java | 8 ++++-
.../org/apache/calcite/test/BabelUnParserTest.java | 30 ++++++++++++++++++
.../main/java/org/apache/calcite/sql/SqlMerge.java | 13 +++++++-
.../java/org/apache/calcite/sql/SqlSetOption.java | 2 +-
.../java/org/apache/calcite/sql/SqlUnpivot.java | 16 ++++++++--
.../org/apache/calcite/sql/ddl/SqlCreateTable.java | 24 ++++++++++++---
.../org/apache/calcite/sql/ddl/SqlCreateView.java | 11 ++++++-
.../apache/calcite/sql/fun/SqlBasicOperator.java | 15 ++++++---
site/_docs/history.md | 6 ++++
.../apache/calcite/sql/parser/SqlParserTest.java | 22 +++++++++++++
11 files changed, 165 insertions(+), 18 deletions(-)
diff --git
a/babel/src/main/java/org/apache/calcite/sql/babel/SqlBabelCreateTable.java
b/babel/src/main/java/org/apache/calcite/sql/babel/SqlBabelCreateTable.java
index 511bef3687..652f0a92e3 100644
--- a/babel/src/main/java/org/apache/calcite/sql/babel/SqlBabelCreateTable.java
+++ b/babel/src/main/java/org/apache/calcite/sql/babel/SqlBabelCreateTable.java
@@ -17,17 +17,39 @@
package org.apache.calcite.sql.babel;
import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.ddl.SqlCreateTable;
+import org.apache.calcite.sql.fun.SqlBasicOperator;
import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.ImmutableNullableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
/**
* Parse tree for {@code CREATE TABLE} statement, with extensions for
particular
* SQL dialects supported by Babel.
*/
public class SqlBabelCreateTable extends SqlCreateTable {
+ private static final SqlOperator OPERATOR =
+ SqlBasicOperator.create("CREATE TABLE",
SqlKind.CREATE_TABLE).withCallFactory(
+ (operator, functionQualifier, pos, operands) ->
+ new SqlBabelCreateTable(pos,
+ requireNonNull((SqlLiteral) operands[0]).booleanValue(),
+ requireNonNull((SqlLiteral)
operands[1]).symbolValue(TableCollectionType.class),
+ requireNonNull((SqlLiteral) operands[2]).booleanValue(),
+ requireNonNull((SqlLiteral) operands[3]).booleanValue(),
+ (SqlIdentifier) requireNonNull(operands[4]),
+ (SqlNodeList) operands[5], operands[6]));
+
private final TableCollectionType tableCollectionType;
// CHECKSTYLE: IGNORE 2; can't use 'volatile' because it is a Java keyword
// but checkstyle does not like trailing '_'.
@@ -36,13 +58,21 @@ public class SqlBabelCreateTable extends SqlCreateTable {
/** Creates a SqlBabelCreateTable. */
public SqlBabelCreateTable(SqlParserPos pos, boolean replace,
TableCollectionType tableCollectionType, boolean volatile_,
- boolean ifNotExists, SqlIdentifier name, SqlNodeList columnList,
- SqlNode query) {
- super(pos, replace, ifNotExists, name, columnList, query);
+ boolean ifNotExists, SqlIdentifier name, @Nullable SqlNodeList
columnList,
+ @Nullable SqlNode query) {
+ super(OPERATOR, pos, replace, ifNotExists, name, columnList, query);
this.tableCollectionType = tableCollectionType;
this.volatile_ = volatile_;
}
+ @SuppressWarnings("nullness")
+ @Override public List<SqlNode> getOperandList() {
+ return ImmutableNullableList.of(SqlLiteral.createBoolean(getReplace(),
pos),
+ SqlLiteral.createSymbol(tableCollectionType, pos),
+ SqlLiteral.createBoolean(volatile_, pos),
+ SqlLiteral.createBoolean(ifNotExists, pos), name, columnList, query);
+ }
+
@Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec)
{
writer.keyword("CREATE");
switch (tableCollectionType) {
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..6eff6b77af 100644
--- a/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
+++ b/babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
@@ -40,6 +40,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasToString;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
/**
* Tests the "Babel" SQL parser, that understands all dialects of SQL.
@@ -390,7 +391,12 @@ private void checkParseInfixCast(String sqlType) {
}
@Test void testPostgresSqlSetOption() {
- SqlParserFixture f = fixture().withDialect(PostgresqlSqlDialect.DEFAULT);
+ // UnparsingTesterImpl has a check where it unparses a SqlNode into a SQL
string
+ // using the calcite dialect, and then parses it back into a SqlNode.
+ // But the SQL string produced by the calcite dialect for `SET` cannot
always be parsed back.
+ assumeFalse(fixture().tester.isUnparserTest());
+ SqlParserFixture f = fixture()
+ .withDialect(PostgresqlSqlDialect.DEFAULT);
f.sql("SET SESSION autovacuum = true")
.ok("SET \"autovacuum\" = TRUE");
f.sql("SET SESSION autovacuum = DEFAULT")
diff --git a/babel/src/test/java/org/apache/calcite/test/BabelUnParserTest.java
b/babel/src/test/java/org/apache/calcite/test/BabelUnParserTest.java
new file mode 100644
index 0000000000..99d6d2286e
--- /dev/null
+++ b/babel/src/test/java/org/apache/calcite/test/BabelUnParserTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.test;
+
+import org.apache.calcite.sql.parser.SqlParserFixture;
+
+/**
+ * Extension to {@link BabelParserTest} that ensures that every expression can
+ * un-parse successfully.
+ */
+public class BabelUnParserTest extends BabelParserTest {
+ @Override public SqlParserFixture fixture() {
+ return super.fixture()
+ .withTester(new UnparsingTesterImpl());
+ }
+}
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlMerge.java
b/core/src/main/java/org/apache/calcite/sql/SqlMerge.java
index cbed8e40a8..ddadf32959 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlMerge.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlMerge.java
@@ -28,13 +28,24 @@
import java.util.List;
+import static java.util.Objects.requireNonNull;
+
/**
* A <code>SqlMerge</code> is a node of a parse tree which represents a MERGE
* statement.
*/
public class SqlMerge extends SqlCall {
public static final SqlSpecialOperator OPERATOR =
- new SqlSpecialOperator("MERGE", SqlKind.MERGE);
+ new SqlSpecialOperator("MERGE", SqlKind.MERGE) {
+ @Override public SqlCall createCall(final @Nullable SqlLiteral
functionQualifier,
+ final SqlParserPos pos,
+ final @Nullable SqlNode... operands) {
+ return new SqlMerge(pos, requireNonNull(operands[0]),
requireNonNull(operands[1]),
+ requireNonNull(operands[2]),
+ (SqlUpdate) operands[3], (SqlInsert) operands[4],
+ (SqlSelect) operands[5], (SqlIdentifier) operands[6]);
+ }
+ };
SqlNode targetTable;
SqlNode condition;
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSetOption.java
b/core/src/main/java/org/apache/calcite/sql/SqlSetOption.java
index 3a7206eb2d..689ba20642 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlSetOption.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlSetOption.java
@@ -143,7 +143,7 @@ public SqlSetOption(SqlParserPos pos, @Nullable String
scope, SqlIdentifier name
} else {
operandList.add(new SqlIdentifier(scope, SqlParserPos.ZERO));
}
- operandList.add(name);
+ operandList.add(nameAsSqlNode);
operandList.add(value);
return ImmutableNullableList.copyOf(operandList);
}
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUnpivot.java
b/core/src/main/java/org/apache/calcite/sql/SqlUnpivot.java
index 528b9ee57d..732e6d4e6a 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlUnpivot.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlUnpivot.java
@@ -67,10 +67,10 @@ public SqlUnpivot(SqlParserPos pos, SqlNode query, boolean
includeNulls,
SqlNodeList measureList, SqlNodeList axisList, SqlNodeList inList) {
super(pos);
this.query = requireNonNull(query, "query");
- this.includeNulls = includeNulls;
this.measureList = requireNonNull(measureList, "measureList");
this.axisList = requireNonNull(axisList, "axisList");
this.inList = requireNonNull(inList, "inList");
+ this.includeNulls = includeNulls;
}
//~ Methods ----------------------------------------------------------------
@@ -80,7 +80,8 @@ public SqlUnpivot(SqlParserPos pos, SqlNode query, boolean
includeNulls,
}
@Override public List<SqlNode> getOperandList() {
- return ImmutableNullableList.of(query, measureList, axisList, inList);
+ return ImmutableNullableList.of(query, measureList, axisList, inList,
+ SqlLiteral.createBoolean(includeNulls, SqlParserPos.ZERO));
}
@SuppressWarnings("nullness")
@@ -176,5 +177,16 @@ static class Operator extends SqlSpecialOperator {
Operator(SqlKind kind) {
super(kind.name(), kind);
}
+
+ @Override public SqlCall createCall(
+ @Nullable SqlLiteral functionQualifier,
+ SqlParserPos pos,
+ @Nullable SqlNode... operands) {
+ return new SqlUnpivot(pos, requireNonNull(operands[0]),
+ requireNonNull((SqlLiteral) operands[4]).booleanValue(),
+ requireNonNull((SqlNodeList) operands[1]),
+ requireNonNull((SqlNodeList) operands[2]),
+ requireNonNull((SqlNodeList) operands[3]));
+ }
}
}
diff --git a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
b/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
index 7e7dd144cb..094059a7c6 100644
--- a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateTable.java
@@ -42,17 +42,33 @@ public class SqlCreateTable extends SqlCreate {
public final @Nullable SqlNode query;
private static final SqlOperator OPERATOR =
- new SqlSpecialOperator("CREATE TABLE", SqlKind.CREATE_TABLE);
+ new SqlSpecialOperator("CREATE TABLE", SqlKind.CREATE_TABLE) {
+ @Override public SqlCall createCall(@Nullable SqlLiteral
functionQualifier,
+ SqlParserPos pos, @Nullable SqlNode... operands) {
+ return new SqlCreateTable(OPERATOR, pos,
+ ((SqlLiteral) requireNonNull(operands[0],
"replace")).booleanValue(),
+ ((SqlLiteral) requireNonNull(operands[1],
"ifNotExists")).booleanValue(),
+ (SqlIdentifier) requireNonNull(operands[2], "name"),
+ (SqlNodeList) operands[3], operands[4]);
+ }
+ };
/** Creates a SqlCreateTable. */
- protected SqlCreateTable(SqlParserPos pos, boolean replace, boolean
ifNotExists,
- SqlIdentifier name, @Nullable SqlNodeList columnList, @Nullable SqlNode
query) {
- super(OPERATOR, pos, replace, ifNotExists);
+ protected SqlCreateTable(SqlOperator operator, SqlParserPos pos, boolean
replace,
+ boolean ifNotExists, SqlIdentifier name, @Nullable SqlNodeList
columnList,
+ @Nullable SqlNode query) {
+ super(operator, pos, replace, ifNotExists);
this.name = requireNonNull(name, "name");
this.columnList = columnList; // may be null
this.query = query; // for "CREATE TABLE ... AS query"; may be null
}
+ /** Creates a SqlCreateTable. */
+ protected SqlCreateTable(SqlParserPos pos, boolean replace, boolean
ifNotExists,
+ SqlIdentifier name, @Nullable SqlNodeList columnList, @Nullable SqlNode
query) {
+ this(OPERATOR, pos, replace, ifNotExists, name, columnList, query);
+ }
+
@SuppressWarnings("nullness")
@Override public List<SqlNode> getOperandList() {
return ImmutableNullableList.of(name, columnList, query);
diff --git a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateView.java
b/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateView.java
index 662e864966..35a0c578fb 100644
--- a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateView.java
+++ b/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateView.java
@@ -42,7 +42,16 @@ public class SqlCreateView extends SqlCreate {
public final SqlNode query;
private static final SqlOperator OPERATOR =
- new SqlSpecialOperator("CREATE VIEW", SqlKind.CREATE_VIEW);
+ new SqlSpecialOperator("CREATE VIEW", SqlKind.CREATE_VIEW) {
+ @Override public SqlCall createCall(@Nullable SqlLiteral
functionQualifier,
+ SqlParserPos pos, @Nullable SqlNode... operands) {
+ return new SqlCreateView(pos,
+ ((SqlLiteral) requireNonNull(operands[0],
"replace")).booleanValue(),
+ (SqlIdentifier) requireNonNull(operands[1], "name"),
+ (SqlNodeList) operands[2],
+ requireNonNull(operands[3], "query"));
+ }
+ };
/** Creates a SqlCreateView. */
SqlCreateView(SqlParserPos pos, boolean replace, SqlIdentifier name,
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlBasicOperator.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlBasicOperator.java
index e84042b5cd..a2dc815ff3 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlBasicOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlBasicOperator.java
@@ -40,24 +40,29 @@ public final class SqlBasicOperator extends SqlOperator {
}
/** Private constructor. Use {@link #create}. */
- private SqlBasicOperator(String name, int leftPrecedence, int
rightPrecedence,
+ private SqlBasicOperator(String name, SqlKind kind, int leftPrecedence, int
rightPrecedence,
SqlCallFactory callFactory) {
- super(name, SqlKind.OTHER, leftPrecedence, rightPrecedence,
+ super(name, kind, leftPrecedence, rightPrecedence,
ReturnTypes.BOOLEAN, InferTypes.RETURN_TYPE, OperandTypes.ANY,
callFactory);
}
public static SqlBasicOperator create(String name) {
- return new SqlBasicOperator(name, 0, 0,
+ return new SqlBasicOperator(name, SqlKind.OTHER, 0, 0,
+ SqlCallFactories.SQL_BASIC_CALL_FACTORY);
+ }
+
+ public static SqlBasicOperator create(String name, SqlKind kind) {
+ return new SqlBasicOperator(name, kind, 0, 0,
SqlCallFactories.SQL_BASIC_CALL_FACTORY);
}
public SqlBasicOperator withPrecedence(int prec, boolean leftAssoc) {
- return new SqlBasicOperator(getName(), leftPrec(prec, leftAssoc),
+ return new SqlBasicOperator(getName(), getKind(), leftPrec(prec,
leftAssoc),
rightPrec(prec, leftAssoc), getSqlCallFactory());
}
public SqlBasicOperator withCallFactory(SqlCallFactory sqlCallFactory) {
- return new SqlBasicOperator(getName(), getLeftPrec(),
+ return new SqlBasicOperator(getName(), getKind(), getLeftPrec(),
getRightPrec(), sqlCallFactory);
}
}
diff --git a/site/_docs/history.md b/site/_docs/history.md
index ea6fd13726..89d91bd266 100644
--- a/site/_docs/history.md
+++ b/site/_docs/history.md
@@ -49,6 +49,12 @@ ## <a
href="https://github.com/apache/calcite/releases/tag/calcite-1.42.0">1.42.
#### Breaking Changes
{: #breaking-1-42-0}
+* [<a
href="https://issues.apache.org/jira/browse/CALCITE-7301">CALCITE-7301</a>]
+Prior to this change, most `SqlNode`s in the `org.apache.calcite.sql.ddl`
package could not be unparsed
+when created with `SqlOperator#createCall`. To fix this, those `SqlNode`s now
implement their own `SqlOperator`.
+`SqlNode#getOperandList()` now returns all operands required by these
operators; the number and order may differ from before.
+The same applies to `SqlBabelCreateTable` and `SqlUnpivot`.
+
* [<a
href="https://issues.apache.org/jira/browse/CALCITE-6942">CALCITE-6942</a>]
Rename the method `decorrelateFetchOneSort` to `decorrelateSortWithRowNumber`.
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 613fe9b7de..cfea5b6ce0 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
@@ -9886,6 +9886,17 @@ static void checkList(SqlNodeList sqlNodeList,
}
}
+ static SqlNode deepCopy(SqlNode sqlNode) {
+ return sqlNode.accept(new SqlShuttle() {
+ @Override public @Nullable SqlNode visit(final SqlCall call) {
+ // Handler always creates a new copy of 'call'
+ CallCopyingArgHandler argHandler = new CallCopyingArgHandler(call,
true);
+ call.getOperator().acceptCall(this, call, false, argHandler);
+ return argHandler.result();
+ }
+ });
+ }
+
@Override public void checkList(SqlTestFactory factory, StringAndPos sap,
@Nullable SqlDialect dialect, UnaryOperator<String> converter,
List<String> expected) {
@@ -9915,6 +9926,12 @@ static void checkList(SqlNodeList sqlNodeList,
final Random random = new Random();
final String sql3 = toSqlString(sqlNodeList, randomize(random));
assertThat(sql3, notNullValue());
+
+ // Make a deep copy of the SqlNodeList, unparse it.
+ final SqlNodeList sqlNodeList3 = (SqlNodeList) deepCopy(sqlNodeList);
+ final String sql4 = toSqlString(sqlNodeList3, simple());
+ // Should be the same as we started with.
+ assertThat(sql4, is(sql1));
}
@Override public void check(SqlTestFactory factory, StringAndPos sap,
@@ -9959,6 +9976,11 @@ static void checkList(SqlNodeList sqlNodeList,
parseStmtAndHandleEx(factory2, sql1, parser -> { });
final String sql4 = sqlNode4.toSqlString(simple()).getSql();
assertThat(sql4, is(sql1));
+
+ // Make a deep copy of the original SqlNode, unparse it.
+ final SqlNode sqlNode5 = deepCopy(sqlNode);
+ final String actual5 = sqlNode5.toSqlString(writerTransform).getSql();
+ assertThat(converter.apply(actual5), is(expected));
}
@Override public void checkExp(SqlTestFactory factory, StringAndPos sap,