This is an automated email from the ASF dual-hosted git repository.

mihaibudiu 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 8d2e09316c [CALCITE-7717] Add a Collect.isValid method to check type 
invariants
8d2e09316c is described below

commit 8d2e09316c495e15d762855309eb6335490e7019
Author: Mihai Budiu <[email protected]>
AuthorDate: Thu Aug 13 16:36:14 2026 -0700

    [CALCITE-7717] Add a Collect.isValid method to check type invariants
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../java/org/apache/calcite/rel/core/Collect.java  | 28 ++++++++
 .../org/apache/calcite/rel/core/CollectTest.java   | 80 ++++++++++++++++++++++
 2 files changed, 108 insertions(+)

diff --git a/core/src/main/java/org/apache/calcite/rel/core/Collect.java 
b/core/src/main/java/org/apache/calcite/rel/core/Collect.java
index 475cc966d3..d2f89eeec3 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Collect.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Collect.java
@@ -29,9 +29,12 @@
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.util.Litmus;
 
 import com.google.common.collect.Iterables;
 
+import org.checkerframework.checker.nullness.qual.Nullable;
+
 import java.util.List;
 
 import static java.util.Objects.requireNonNull;
@@ -184,6 +187,31 @@ public RelNode copy(RelTraitSet traitSet, RelNode input) {
     return new Collect(getCluster(), traitSet, input, rowType());
   }
 
+  @Override public boolean isValid(Litmus litmus, @Nullable Context context) {
+    final RelDataTypeFactory typeFactory = getCluster().getTypeFactory();
+    final RelDataType inputRow = getInput().getRowType();
+    if (getCollectionType() == SqlTypeName.MAP && inputRow.getFieldCount() != 
2) {
+      return litmus.fail("MAP requires an input with exactly two fields;"
+          + " input row type is {}", inputRow);
+    }
+    final RelDataType derived =
+        deriveRowType(typeFactory, getCollectionType(), getFieldName(), 
inputRow);
+    if (rowType().equals(derived)) {
+      return super.isValid(litmus, context);
+    }
+    // A Collect created for a collection query constructor derives its element
+    // type from the input row type; see #create(RelNode, SqlKind, String).
+    final RelDataType derivedForQuery =
+        deriveRowType(typeFactory, getCollectionType(), getFieldName(),
+            SqlTypeUtil.deriveCollectionQueryComponentType(typeFactory,
+                getCollectionType(), inputRow));
+    if (rowType().equals(derivedForQuery)) {
+      return super.isValid(litmus, context);
+    }
+    return litmus.fail("row type {} does not match the type {}"
+        + " derived from the input", rowType(), derived);
+  }
+
   @Override public RelNode accept(RelShuttle shuttle) {
     return shuttle.visit(this);
   }
diff --git a/core/src/test/java/org/apache/calcite/rel/core/CollectTest.java 
b/core/src/test/java/org/apache/calcite/rel/core/CollectTest.java
new file mode 100644
index 0000000000..694eebf1f9
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/rel/core/CollectTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.rel.core;
+
+import org.apache.calcite.plan.Convention;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.test.RelBuilderTest;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.util.Litmus;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Test cases for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7717";>[CALCITE-7717]
+ * Add a Collect.isValid method to check type invariants</a>.
+ */
+class CollectTest {
+  @Test void testIsValid() {
+    final RelBuilder b = RelBuilder.create(RelBuilderTest.config().build());
+    final RelNode oneColumn = b.values(new String[] {"i"}, 1, 2).build();
+    final RelDataTypeFactory typeFactory = 
oneColumn.getCluster().getTypeFactory();
+
+    // Element type is the input row type.  'x' is the name of the result field
+    final Collect collect0 = Collect.create(oneColumn, 
SqlKind.ARRAY_QUERY_CONSTRUCTOR, "x");
+    assertThat(collect0.isValid(Litmus.IGNORE, null), is(true));
+
+    // Element type is the type of the sole input column.
+    final Collect collect1 =
+        new Collect(oneColumn.getCluster(),
+            oneColumn.getCluster().traitSetOf(Convention.NONE), oneColumn,
+            Collect.deriveRowType(typeFactory, SqlTypeName.ARRAY, "x",
+                oneColumn.getRowType().getFieldList().get(0).getType()));
+    assertThat(collect1.isValid(Litmus.IGNORE, null), is(true));
+
+    // Array over two columns is invalid
+    final RelNode twoColumns = b.values(new String[] {"k", "v"}, 1, 
"a").build();
+    final Collect mismatched =
+            new Collect(oneColumn.getCluster(),
+            oneColumn.getCluster().traitSetOf(Convention.NONE), oneColumn,
+            Collect.deriveRowType(typeFactory, SqlTypeName.ARRAY, "x",
+                twoColumns.getRowType()));
+    assertThat(mismatched.isValid(Litmus.IGNORE, null), is(false));
+
+    final RelDataType mapRowType =
+        Collect.deriveRowType(typeFactory, SqlTypeName.MAP, "x", 
twoColumns.getRowType());
+
+   // A MAP(subquery) over an input that does not have exactly two columns is 
invalid
+    final Collect mapOverOneColumn =
+        new Collect(oneColumn.getCluster(),
+            oneColumn.getCluster().traitSetOf(Convention.NONE), oneColumn, 
mapRowType);
+    assertThat(mapOverOneColumn.isValid(Litmus.IGNORE, null), is(false));
+
+    // The same MAP row type over the two-column input is valid.
+    final Collect mapOverTwoColumns =
+        new Collect(twoColumns.getCluster(),
+            twoColumns.getCluster().traitSetOf(Convention.NONE), twoColumns, 
mapRowType);
+    assertThat(mapOverTwoColumns.isValid(Litmus.IGNORE, null), is(true));
+  }
+}

Reply via email to