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 7178b54f6d [CALCITE-7842] Multiset operators return incorrect results
for ROW elements
7178b54f6d is described below
commit 7178b54f6d321eb59801ff3a88aa50af74678e06
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Fri Oct 9 12:43:38 2026 +0300
[CALCITE-7842] Multiset operators return incorrect results for ROW elements
---
.../org/apache/calcite/runtime/SqlFunctions.java | 159 ++--
.../org/apache/calcite/util/BuiltInMethod.java | 24 +-
.../org/apache/calcite/test/SqlFunctionsTest.java | 113 +++
core/src/test/resources/sql/row-equality.iq | 842 +++++++++++++++++++++
4 files changed, 1057 insertions(+), 81 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 4dfc4f542f..2b3a45c479 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -131,7 +131,6 @@
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -144,6 +143,7 @@
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
@@ -7096,7 +7096,7 @@ private static AtomicLong getAtomicLong(String key) {
final List bigger = list2;
boolean hasNull = false;
if (!smaller.isEmpty() && !bigger.isEmpty()) {
- final Set<@Nullable Object> smallestSet = toDeepEqualitySet(smaller);
+ final Set<@Nullable Object> smallestSet = toDeepEqualityHashSet(smaller);
hasNull = smallestSet.remove(null);
for (Object element : bigger) {
if (element == null) {
@@ -7170,7 +7170,7 @@ public static List arrayAppend(List list, Object element)
{
/** Support the ARRAY_DISTINCT function. */
public static List distinct(List list) {
- return unwrapDeepEqualitySet(toDeepEqualitySet(list));
+ return unwrapDeepEqualitySet(toDeepEqualityLinkedHashSet(list));
}
/** Support the ARRAY_MAX function. */
@@ -7252,8 +7252,10 @@ public static List arrayRemove(List list, Object
element) {
/** Support the ARRAY_EXCEPT function. */
public static List arrayExcept(List list1, List list2) {
- final Set<@Nullable Object> result = toDeepEqualitySet(list1);
- result.removeAll(toDeepEqualitySet(list2));
+ final Set<@Nullable Object> result = toDeepEqualityLinkedHashSet(list1);
+ for (Object element : list2) {
+ result.remove(wrapForDeepEquality(element));
+ }
return unwrapDeepEqualitySet(result);
}
@@ -7354,30 +7356,54 @@ public static List arrayExcept(List list1, List list2) {
/** Support the ARRAY_INTERSECT function. */
public static List arrayIntersect(List list1, List list2) {
- final Set<@Nullable Object> result = toDeepEqualitySet(list1);
- result.retainAll(toDeepEqualitySet(list2));
- return unwrapDeepEqualitySet(result);
+ final Set<@Nullable Object> remaining = toDeepEqualityHashSet(list2);
+ final List<@Nullable Object> result = new ArrayList<>();
+ for (Object element : list1) {
+ if (remaining.remove(wrapForDeepEquality(element))) {
+ result.add(element);
+ }
+ }
+ return result;
}
/** Support the ARRAY_UNION function. */
public static List arrayUnion(List list1, List list2) {
- final Set<@Nullable Object> result = toDeepEqualitySet(list1);
+ final Set<@Nullable Object> result = toDeepEqualityLinkedHashSet(list1);
for (Object element : list2) {
result.add(wrapForDeepEquality(element));
}
return unwrapDeepEqualitySet(result);
}
- /** Creates a set using deep equality, preserving element encounter order.
- * This preserves the ordering required by ARRAY_DISTINCT. */
- private static Set<@Nullable Object> toDeepEqualitySet(List<?> list) {
- final Set<@Nullable Object> set = new LinkedHashSet<>();
+ /** Creates a set using deep equality, preserving element encounter order. */
+ private static Set<@Nullable Object> toDeepEqualityLinkedHashSet(List<?>
list) {
+ return toDeepEqualitySet(list, LinkedHashSet::new);
+ }
+
+ /** Creates a hash set using deep equality. */
+ private static Set<@Nullable Object> toDeepEqualityHashSet(List<?> list) {
+ return toDeepEqualitySet(list, HashSet::new);
+ }
+
+ /** Creates a set using deep equality and the supplied factory. */
+ private static Set<@Nullable Object> toDeepEqualitySet(List<?> list,
+ Supplier<? extends Set<@Nullable Object>> setSupplier) {
+ final Set<@Nullable Object> set = setSupplier.get();
for (Object value : list) {
set.add(wrapForDeepEquality(value));
}
return set;
}
+ /** Counts element occurrences using deep equality. */
+ private static Map<@Nullable Object, Integer> toDeepEqualityCounts(List<?>
list) {
+ final Map<@Nullable Object, Integer> counts = new HashMap<>();
+ for (Object value : list) {
+ counts.merge(wrapForDeepEquality(value), 1, Integer::sum);
+ }
+ return counts;
+ }
+
/** Wraps composite values, which may contain ROW values, but leaves scalar
* values and null unchanged. */
private static @Nullable Object wrapForDeepEquality(@Nullable Object value) {
@@ -7695,61 +7721,66 @@ public static List slice(List list) {
}
/** Support the MEMBER OF function. */
- public static boolean memberOf(@Nullable Object object, Collection
collection) {
- return collection.contains(object);
+ public static boolean memberOf(@Nullable Object object, List<?> list) {
+ for (Object value : list) {
+ if (Functions.deepEquals(value, object)) {
+ return true;
+ }
+ }
+ return false;
}
/** Support the MULTISET INTERSECT DISTINCT function. */
- public static <E> Collection<E> multisetIntersectDistinct(Collection<E> c1,
- Collection<E> c2) {
- final Set<E> result = new HashSet<>(c1);
- result.retainAll(c2);
- return new ArrayList<>(result);
+ public static List multisetIntersectDistinct(List c1, List c2) {
+ return arrayIntersect(c1, c2);
}
/** Support the MULTISET INTERSECT ALL function. */
- public static <E> Collection<E> multisetIntersectAll(Collection<E> c1,
- Collection<E> c2) {
- final List<E> result = new ArrayList<>(c1.size());
- final List<E> c2Copy = new ArrayList<>(c2);
- for (E e : c1) {
- if (c2Copy.remove(e)) {
+ public static List multisetIntersectAll(List c1, List c2) {
+ final Map<@Nullable Object, Integer> counts = toDeepEqualityCounts(c2);
+ final List result = new ArrayList();
+ for (Object e : c1) {
+ counts.computeIfPresent(wrapForDeepEquality(e), (key, count) -> {
result.add(e);
- }
+ return count == 1 ? null : count - 1;
+ });
}
+
return result;
}
/** Support the MULTISET EXCEPT ALL function. */
- @SuppressWarnings("JdkObsolete")
- public static <E> Collection<E> multisetExceptAll(Collection<E> c1,
- Collection<E> c2) {
- // TOOD: use Multisets?
- final List<E> result = new LinkedList<>(c1);
- for (E e : c2) {
- result.remove(e);
+ public static List multisetExceptAll(List c1, List c2) {
+ final Map<@Nullable Object, Integer> counts = toDeepEqualityCounts(c2);
+ final List result = new ArrayList();
+ for (Object e : c1) {
+ counts.compute(wrapForDeepEquality(e), (key, count) -> {
+ if (count == null) {
+ result.add(e);
+ return null;
+ }
+ return count == 1 ? null : count - 1;
+ });
}
return result;
}
/** Support the MULTISET EXCEPT DISTINCT function. */
- public static <E> Collection<E> multisetExceptDistinct(Collection<E> c1,
- Collection<E> c2) {
- final Set<E> result = new HashSet<>(c1);
- result.removeAll(c2);
- return new ArrayList<>(result);
+ public static List multisetExceptDistinct(List c1, List c2) {
+ final Set<@Nullable Object> result = toDeepEqualityHashSet(c1);
+ for (Object e : c2) {
+ result.remove(wrapForDeepEquality(e));
+ }
+ return unwrapDeepEqualitySet(result);
}
/** Support the IS A SET function. */
- public static boolean isASet(Collection collection) {
- if (collection instanceof Set) {
- return true;
- }
+ public static boolean isASet(List<?> list) {
// capacity calculation is in the same way like for new HashSet(Collection)
// however return immediately in case of duplicates
- Set set = new HashSet(Math.max((int) (collection.size() / .75f) + 1, 16));
- for (Object e : collection) {
- if (!set.add(e)) {
+ final Set<@Nullable Object> set = new HashSet<>(Math.max((int)
(list.size() / .75f) + 1, 16));
+ for (Object e : list) {
+ if (!set.add(wrapForDeepEquality(e))) {
return false;
}
}
@@ -7757,16 +7788,15 @@ public static boolean isASet(Collection collection) {
}
/** Support the SUBMULTISET OF function. */
- @SuppressWarnings("JdkObsolete")
- public static boolean submultisetOf(Collection possibleSubMultiset,
- Collection multiset) {
+ public static boolean submultisetOf(List<?> possibleSubMultiset, List<?>
multiset) {
if (possibleSubMultiset.size() > multiset.size()) {
return false;
}
- // TODO: use Multisets?
- Collection multisetLocal = new LinkedList(multiset);
+ final Map<@Nullable Object, Integer> counts =
toDeepEqualityCounts(multiset);
for (Object e : possibleSubMultiset) {
- if (!multisetLocal.remove(e)) {
+ final Integer remaining =
+ counts.computeIfPresent(wrapForDeepEquality(e), (key, count) ->
count - 1);
+ if (remaining == null || remaining < 0) {
return false;
}
}
@@ -7774,23 +7804,20 @@ public static boolean submultisetOf(Collection
possibleSubMultiset,
}
/** Support the MULTISET UNION function. */
- public static Collection multisetUnionDistinct(Collection collection1,
- Collection collection2) {
- // capacity calculation is in the same way like for new HashSet(Collection)
- Set resultCollection =
- new HashSet(Math.max((int) ((collection1.size() + collection2.size())
/ .75f) + 1, 16));
- resultCollection.addAll(collection1);
- resultCollection.addAll(collection2);
- return new ArrayList(resultCollection);
+ public static List multisetUnionDistinct(List list1, List list2) {
+ final Set<@Nullable Object> result = toDeepEqualityHashSet(list1);
+ for (Object element : list2) {
+ result.add(wrapForDeepEquality(element));
+ }
+ return unwrapDeepEqualitySet(result);
}
/** Support the MULTISET UNION ALL function. */
- public static Collection multisetUnionAll(Collection collection1,
- Collection collection2) {
- List resultCollection = new ArrayList(collection1.size() +
collection2.size());
- resultCollection.addAll(collection1);
- resultCollection.addAll(collection2);
- return resultCollection;
+ public static List multisetUnionAll(List list1, List list2) {
+ final List result = new ArrayList(list1.size() + list2.size());
+ result.addAll(list1);
+ result.addAll(list2);
+ return result;
}
/** Support the ARRAY_REVERSE function. */
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 4f4a021c6d..6aba599523 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -905,23 +905,17 @@ public enum BuiltInMethod {
SEQUENCE_NEXT_VALUE(SqlFunctions.class, "sequenceNextValue", String.class),
SLICE(SqlFunctions.class, "slice", List.class),
ELEMENT(SqlFunctions.class, "element", List.class),
- MEMBER_OF(SqlFunctions.class, "memberOf", Object.class, Collection.class),
+ MEMBER_OF(SqlFunctions.class, "memberOf", Object.class, List.class),
MULTISET_INTERSECT_DISTINCT(SqlFunctions.class, "multisetIntersectDistinct",
- Collection.class, Collection.class),
- MULTISET_INTERSECT_ALL(SqlFunctions.class, "multisetIntersectAll",
- Collection.class, Collection.class),
- MULTISET_EXCEPT_DISTINCT(SqlFunctions.class, "multisetExceptDistinct",
- Collection.class, Collection.class),
- MULTISET_EXCEPT_ALL(SqlFunctions.class, "multisetExceptAll",
- Collection.class, Collection.class),
- MULTISET_UNION_DISTINCT(SqlFunctions.class, "multisetUnionDistinct",
- Collection.class, Collection.class),
- MULTISET_UNION_ALL(SqlFunctions.class, "multisetUnionAll", Collection.class,
- Collection.class),
- IS_A_SET(SqlFunctions.class, "isASet", Collection.class),
+ List.class, List.class),
+ MULTISET_INTERSECT_ALL(SqlFunctions.class, "multisetIntersectAll",
List.class, List.class),
+ MULTISET_EXCEPT_DISTINCT(SqlFunctions.class, "multisetExceptDistinct",
List.class, List.class),
+ MULTISET_EXCEPT_ALL(SqlFunctions.class, "multisetExceptAll", List.class,
List.class),
+ MULTISET_UNION_DISTINCT(SqlFunctions.class, "multisetUnionDistinct",
List.class, List.class),
+ MULTISET_UNION_ALL(SqlFunctions.class, "multisetUnionAll", List.class,
List.class),
+ IS_A_SET(SqlFunctions.class, "isASet", List.class),
IS_EMPTY(Collection.class, "isEmpty"),
- SUBMULTISET_OF(SqlFunctions.class, "submultisetOf", Collection.class,
- Collection.class),
+ SUBMULTISET_OF(SqlFunctions.class, "submultisetOf", List.class, List.class),
ARRAY_APPEND(SqlFunctions.class, "arrayAppend", List.class, Object.class),
CYCLE_PATH_CONTAINS(SqlFunctions.class, "cyclePathContains", List.class,
Object.class),
ARRAY_COMPACT(SqlFunctions.class, "compact", List.class),
diff --git a/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
b/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
index fa29ceebbf..da266bc7ad 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlFunctionsTest.java
@@ -1649,6 +1649,117 @@ private void thereAndBack(byte[] bytes) {
isListOf("a", "c", "d"));
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7842">[CALCITE-7842]
+ * Multiset operators return incorrect results for ROW elements</a>. */
+ @Test void testMultisetWithRows() {
+ final Object row = new Object[] {1, "Foo"};
+ final Object equalRow = new Object[] {1, "Foo"};
+ final Object otherRow = new Object[] {2, "Bar"};
+ final List<Object> left = list(row, equalRow, otherRow);
+ final List<Object> right = list(equalRow);
+ assertThat(SqlFunctions.memberOf(row, right), is(true));
+ assertThat(SqlFunctions.memberOf(otherRow, right), is(false));
+ assertThat(SqlFunctions.isASet(left), is(false));
+ assertThat(SqlFunctions.isASet(list(row, otherRow)), is(true));
+ assertThat(SqlFunctions.submultisetOf(list(row, otherRow), left),
is(true));
+ assertThat(SqlFunctions.submultisetOf(list(row, equalRow), right),
is(false));
+ assertThat(SqlFunctions.multisetIntersectAll(left, right), is(list(row)));
+ assertThat(SqlFunctions.multisetIntersectDistinct(left, right),
is(list(row)));
+ assertThat(SqlFunctions.multisetExceptAll(left, right), is(list(equalRow,
otherRow)));
+ assertThat(SqlFunctions.multisetExceptDistinct(left, right),
is(list(otherRow)));
+ final List<?> union = SqlFunctions.multisetUnionDistinct(list(row,
equalRow), right);
+ assertThat(union, hasSize(1));
+ assertSame(row, union.get(0));
+ assertThat(SqlFunctions.multisetUnionAll(left, right),
+ is(list(row, equalRow, otherRow, equalRow)));
+ assertThat(left, is(list(row, equalRow, otherRow)));
+ assertThat(right, is(list(equalRow)));
+
+ final List<?> distinct =
+ SqlFunctions.multisetUnionDistinct(Collections.singletonList(new
Object[] {-31}),
+ Collections.singletonList(new Object[] {-30}));
+ assertThat(distinct, hasSize(2));
+ }
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7842">[CALCITE-7842]
+ * Multiset operators return incorrect results for ROW elements</a>. */
+ @Test void testMultisetIntersectAllCounts() {
+ final Object a1 = new Object[] {1, null};
+ final Object a2 = new Object[] {1, null};
+ final Object b = new Object[] {2, "Bar"};
+ final Object equalB = new Object[] {2, "Bar"};
+ final List<Object> left = list(a1, b, a2, null, new Object[] {1, null}, b,
null);
+ final List<Object> right = list(equalB, a2, null, a1, null, null, "other");
+ assertThat(SqlFunctions.multisetIntersectAll(left, right), is(list(a1, b,
a2, null, null)));
+ assertThat(SqlFunctions.multisetIntersectAll(right, left),
+ is(list(right.get(0), right.get(1), null, right.get(3), null)));
+
+ // Deep hash zero and its replacement collide, but represent different
rows.
+ final Object zeroHash = new Object[] {-31};
+ final Object collision = new Object[] {-30};
+ assertThat(
+ SqlFunctions.multisetIntersectAll(list(zeroHash, collision, zeroHash),
+ list(new Object[] {-31}, new Object[] {-30}, new Object[] {-30})),
+ is(list(zeroHash, collision)));
+ }
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7842">[CALCITE-7842]
+ * Multiset operators return incorrect results for ROW elements</a>. */
+ @Test void testMultisetExceptAllCounts() {
+ final Object a1 = new Object[] {1, null};
+ final Object a2 = new Object[] {1, null};
+ final Object a3 = new Object[] {1, null};
+ final Object b = new Object[] {2, "Bar"};
+ final Object equalB = new Object[] {2, "Bar"};
+ final List<Object> left = list(a1, b, a2, null, a3, b, null);
+ final List<Object> right = list(equalB, a2, null, a1, null, null, "other");
+ assertThat(SqlFunctions.multisetExceptAll(left, right), is(list(a3, b)));
+ assertThat(SqlFunctions.multisetExceptAll(right, left), is(list(null,
"other")));
+ assertThat(SqlFunctions.multisetExceptAll(left, list()), is(left));
+ assertThat(SqlFunctions.multisetExceptAll(left, left), is(list()));
+ assertThat(left, is(list(a1, b, a2, null, a3, b, null)));
+ assertThat(right, is(list(equalB, a2, null, a1, null, null, "other")));
+
+ // Deep hash zero and its replacement collide, but represent different
rows.
+ final Object zeroHash = new Object[] {-31};
+ final Object collision = new Object[] {-30};
+ assertThat(
+ SqlFunctions.multisetExceptAll(list(zeroHash, collision, zeroHash),
+ list(new Object[] {-31}, new Object[] {-30}, new Object[] {-30})),
+ is(Collections.singletonList(zeroHash)));
+ }
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7842">[CALCITE-7842]
+ * Multiset operators return incorrect results for ROW elements</a>. */
+ @Test void testSubmultisetOfCounts() {
+ final Object row = new Object[] {1, null};
+ final Object equalRow = new Object[] {1, null};
+ final Object other = new Object[] {2, null};
+ final List<Object> sub = list(row, equalRow, null);
+ final List<Object> multiset = list(equalRow, other, null);
+ assertThat(SqlFunctions.submultisetOf(sub, multiset), is(false));
+ assertThat(SqlFunctions.submultisetOf(sub, list(equalRow, row, null,
other)), is(true));
+ assertThat(SqlFunctions.submultisetOf(list(null, null), list(null,
other)), is(false));
+ assertThat(SqlFunctions.submultisetOf(list(null, null), list(null, null)),
is(true));
+ assertThat(SqlFunctions.submultisetOf(list(), list()), is(true));
+ assertThat(sub, is(list(row, equalRow, null)));
+ assertThat(multiset, is(list(equalRow, other, null)));
+
+ // Deep hash zero and its replacement collide, but represent different
rows.
+ final Object zeroHash = new Object[] {-31};
+ final Object collision = new Object[] {-30};
+ assertThat(
+ SqlFunctions.submultisetOf(list(zeroHash, zeroHash),
+ list(new Object[] {-31}, collision)), is(false));
+ assertThat(
+ SqlFunctions.submultisetOf(list(zeroHash, collision),
+ list(new Object[] {-30}, new Object[] {-31})), is(true));
+ }
+
@Test void testMd5() {
assertThat("d41d8cd98f00b204e9800998ecf8427e", is(md5("")));
assertThat("d41d8cd98f00b204e9800998ecf8427e", is(md5(ByteString.of("",
16))));
@@ -2582,6 +2693,8 @@ private static void checkArrayFunctionsWithRows(Object
row, Object equalRow) {
is(list(row, null, other)));
assertThat(SqlFunctions.arrayIntersect(input, list(equalRow, null)),
is(list(row, null)));
+ assertThat(SqlFunctions.arrayIntersect(input, list(null, equalRow, null,
equalRow)),
+ is(list(row, null)));
assertThat(SqlFunctions.arrayExcept(input, list(equalRow, null)),
is(list(other)));
assertThat(arraysOverlap(input, list(equalRow)), is(true));
assertThat(arraysOverlap(list(row), list(other)), is(false));
diff --git a/core/src/test/resources/sql/row-equality.iq
b/core/src/test/resources/sql/row-equality.iq
index b83302a006..aa9de0d6fc 100644
--- a/core/src/test/resources/sql/row-equality.iq
+++ b/core/src/test/resources/sql/row-equality.iq
@@ -1011,4 +1011,846 @@ FROM (VALUES (1, 'Foo', 1, 'Foo'), (2, 'Bar', 2,
'Car')) AS t(x, y, z, w);
!ok
+# Test cases for https://issues.apache.org/jira/browse/CALCITE-7842
+# [CALCITE-7842] Multiset operators return incorrect results for ROW elements
+# All 31 queries below were checked against Oracle using OneCompiler.
+# The Oracle adaptation used object types in place of ROW values and nested
+# table types in place of MULTISET literals. Explicit MAP methods compared
+# object fields, treating corresponding NULL fields as equal.
+# All 217 result rows matched after ordering by case id and element fields.
+!use scott
+
+# Original MEMBER OF reproduction.
+SELECT ROW(1, 'Foo') MEMBER OF MULTISET[ROW(x, y)] AS r
+FROM (VALUES (1, 'Foo'), (2, 'Bar')) AS t(x, y);
++-------+
+| R |
++-------+
+| true |
+| false |
++-------+
+(2 rows)
+
+!ok
+
+# Flat ROW values.
+
+# MEMBER OF
+SELECT id, ROW(x, y) MEMBER OF MULTISET[ROW(z, w)] AS r
+FROM (VALUES
+ (1, 1, 'Foo', 1, 'Foo'),
+ (2, 2, 'Bar', 2, 'Car'),
+ (3, 3, 'Baz', 4, 'Baz'),
+ (4, 5, NULL, 5, NULL)) AS t(id, x, y, z, w)
+ORDER BY id;
++----+-------+
+| ID | R |
++----+-------+
+| 1 | true |
+| 2 | false |
+| 3 | false |
+| 4 | true |
++----+-------+
+(4 rows)
+
+!ok
+
+# IS A SET and IS NOT A SET
+SELECT id,
+ MULTISET[ROW(x, y), ROW(z, w)] IS A SET AS r,
+ MULTISET[ROW(x, y), ROW(z, w)] IS NOT A SET AS not_r
+FROM (VALUES
+ (1, 1, 'Foo', 1, 'Foo'),
+ (2, 2, 'Bar', 2, 'Car'),
+ (3, 3, 'Baz', 4, 'Baz'),
+ (4, 5, NULL, 5, NULL)) AS t(id, x, y, z, w)
+ORDER BY id;
++----+-------+-------+
+| ID | R | NOT_R |
++----+-------+-------+
+| 1 | false | true |
+| 2 | true | false |
+| 3 | true | false |
+| 4 | false | true |
++----+-------+-------+
+(4 rows)
+
+!ok
+
+# SUBMULTISET OF and NOT SUBMULTISET OF
+SELECT id,
+ MULTISET[ROW(x, y), ROW(x, y)]
+ SUBMULTISET OF MULTISET[ROW(z, w), ROW(z, w), ROW(z, w), ROW(9,
'Zzz')] AS r,
+ MULTISET[ROW(x, y), ROW(x, y)]
+ NOT SUBMULTISET OF MULTISET[ROW(z, w), ROW(z, w), ROW(z, w), ROW(9,
'Zzz')] AS not_r
+FROM (VALUES
+ (1, 1, 'Foo', 1, 'Foo'),
+ (2, 2, 'Bar', 2, 'Car'),
+ (3, 3, 'Baz', 4, 'Baz'),
+ (4, 5, NULL, 5, NULL)) AS t(id, x, y, z, w)
+ORDER BY id;
++----+-------+-------+
+| ID | R | NOT_R |
++----+-------+-------+
+| 1 | true | false |
+| 2 | false | true |
+| 3 | false | true |
+| 4 | true | false |
++----+-------+-------+
+(4 rows)
+
+!ok
+
+# Equal input cardinalities, but too few occurrences of the requested row.
+SELECT id, MULTISET[ROW(x, y), ROW(x, y)]
+ SUBMULTISET OF MULTISET[ROW(z, w), ROW(9, 'Zzz')] AS r
+FROM (VALUES
+ (1, 1, 'Foo', 1, 'Foo'),
+ (2, 2, 'Bar', 2, 'Car'),
+ (3, 3, 'Baz', 4, 'Baz'),
+ (4, 5, NULL, 5, NULL)) AS t(id, x, y, z, w)
+ORDER BY id;
++----+-------+
+| ID | R |
++----+-------+
+| 1 | false |
+| 2 | false |
+| 3 | false |
+| 4 | false |
++----+-------+
+(4 rows)
+
+!ok
+
+# MULTISET INTERSECT ALL
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, y), ROW(x, y), ROW(9, 'Zzz')]
+ MULTISET INTERSECT ALL
+ MULTISET[ROW(z, w), ROW(z, w), ROW(z, w), ROW(8, 'Yyy')] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 1, 'Foo'),
+ (2, 2, 'Bar', 2, 'Car'),
+ (3, 3, 'Baz', 4, 'Baz'),
+ (4, 5, NULL, 5, NULL)) AS t(id, x, y, z, w)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+-----------+
+| ID | R |
++----+-----------+
+| 1 | {1, Foo} |
+| 1 | {1, Foo} |
+| 4 | {5, null} |
+| 4 | {5, null} |
++----+-----------+
+(4 rows)
+
+!ok
+
+# MULTISET INTERSECT DISTINCT
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, y), ROW(x, y), ROW(9, 'Zzz')]
+ MULTISET INTERSECT DISTINCT
+ MULTISET[ROW(z, w), ROW(z, w), ROW(z, w), ROW(8, 'Yyy')] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 1, 'Foo'),
+ (2, 2, 'Bar', 2, 'Car'),
+ (3, 3, 'Baz', 4, 'Baz'),
+ (4, 5, NULL, 5, NULL)) AS t(id, x, y, z, w)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+-----------+
+| ID | R |
++----+-----------+
+| 1 | {1, Foo} |
+| 4 | {5, null} |
++----+-----------+
+(2 rows)
+
+!ok
+
+# MULTISET EXCEPT ALL
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, y), ROW(x, y), ROW(9, 'Zzz')]
+ MULTISET EXCEPT ALL
+ MULTISET[ROW(z, w), ROW(z, w), ROW(z, w), ROW(8, 'Yyy')] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 1, 'Foo'),
+ (2, 2, 'Bar', 2, 'Car'),
+ (3, 3, 'Baz', 4, 'Baz'),
+ (4, 5, NULL, 5, NULL)) AS t(id, x, y, z, w)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+----------+
+| ID | R |
++----+----------+
+| 1 | {9, Zzz} |
+| 2 | {2, Bar} |
+| 2 | {2, Bar} |
+| 2 | {9, Zzz} |
+| 3 | {3, Baz} |
+| 3 | {3, Baz} |
+| 3 | {9, Zzz} |
+| 4 | {9, Zzz} |
++----+----------+
+(8 rows)
+
+!ok
+
+# MULTISET EXCEPT DISTINCT
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, y), ROW(x, y), ROW(9, 'Zzz')]
+ MULTISET EXCEPT DISTINCT
+ MULTISET[ROW(z, w), ROW(z, w), ROW(z, w), ROW(8, 'Yyy')] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 1, 'Foo'),
+ (2, 2, 'Bar', 2, 'Car'),
+ (3, 3, 'Baz', 4, 'Baz'),
+ (4, 5, NULL, 5, NULL)) AS t(id, x, y, z, w)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+----------+
+| ID | R |
++----+----------+
+| 1 | {9, Zzz} |
+| 2 | {2, Bar} |
+| 2 | {9, Zzz} |
+| 3 | {3, Baz} |
+| 3 | {9, Zzz} |
+| 4 | {9, Zzz} |
++----+----------+
+(6 rows)
+
+!ok
+
+# MULTISET UNION DISTINCT
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, y), ROW(x, y), ROW(9, 'Zzz')]
+ MULTISET UNION DISTINCT
+ MULTISET[ROW(z, w), ROW(z, w), ROW(z, w), ROW(8, 'Yyy')] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 1, 'Foo'),
+ (2, 2, 'Bar', 2, 'Car'),
+ (3, 3, 'Baz', 4, 'Baz'),
+ (4, 5, NULL, 5, NULL)) AS t(id, x, y, z, w)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+-----------+
+| ID | R |
++----+-----------+
+| 1 | {1, Foo} |
+| 1 | {8, Yyy} |
+| 1 | {9, Zzz} |
+| 2 | {2, Bar} |
+| 2 | {2, Car} |
+| 2 | {8, Yyy} |
+| 2 | {9, Zzz} |
+| 3 | {3, Baz} |
+| 3 | {4, Baz} |
+| 3 | {8, Yyy} |
+| 3 | {9, Zzz} |
+| 4 | {5, null} |
+| 4 | {8, Yyy} |
+| 4 | {9, Zzz} |
++----+-----------+
+(14 rows)
+
+!ok
+
+# MULTISET UNION ALL
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, y), ROW(x, y), ROW(9, 'Zzz')]
+ MULTISET UNION ALL
+ MULTISET[ROW(z, w), ROW(z, w), ROW(z, w), ROW(8, 'Yyy')] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 1, 'Foo'),
+ (2, 2, 'Bar', 2, 'Car'),
+ (3, 3, 'Baz', 4, 'Baz'),
+ (4, 5, NULL, 5, NULL)) AS t(id, x, y, z, w)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+-----------+
+| ID | R |
++----+-----------+
+| 1 | {1, Foo} |
+| 1 | {1, Foo} |
+| 1 | {1, Foo} |
+| 1 | {1, Foo} |
+| 1 | {1, Foo} |
+| 1 | {8, Yyy} |
+| 1 | {9, Zzz} |
+| 2 | {2, Bar} |
+| 2 | {2, Bar} |
+| 2 | {2, Car} |
+| 2 | {2, Car} |
+| 2 | {2, Car} |
+| 2 | {8, Yyy} |
+| 2 | {9, Zzz} |
+| 3 | {3, Baz} |
+| 3 | {3, Baz} |
+| 3 | {4, Baz} |
+| 3 | {4, Baz} |
+| 3 | {4, Baz} |
+| 3 | {8, Yyy} |
+| 3 | {9, Zzz} |
+| 4 | {5, null} |
+| 4 | {5, null} |
+| 4 | {5, null} |
+| 4 | {5, null} |
+| 4 | {5, null} |
+| 4 | {8, Yyy} |
+| 4 | {9, Zzz} |
++----+-----------+
+(28 rows)
+
+!ok
+
+# Nested ROW values.
+
+# MEMBER OF
+SELECT id, ROW(ROW(x, y), p) MEMBER OF MULTISET[ROW(ROW(z, w), q)] AS r
+FROM (VALUES
+ (1, 1, 'Foo', 10, 1, 'Foo', 10),
+ (2, 2, 'Bar', 10, 2, 'Car', 10),
+ (3, 3, 'Baz', 10, 4, 'Baz', 10),
+ (4, 5, NULL, 10, 5, NULL, 10),
+ (5, 6, 'Qux', 10, 6, 'Qux', 11)) AS t(id, x, y, p, z, w, q)
+ORDER BY id;
++----+-------+
+| ID | R |
++----+-------+
+| 1 | true |
+| 2 | false |
+| 3 | false |
+| 4 | true |
+| 5 | false |
++----+-------+
+(5 rows)
+
+!ok
+
+# IS A SET and IS NOT A SET
+SELECT id,
+ MULTISET[ROW(ROW(x, y), p), ROW(ROW(z, w), q)] IS A SET AS r,
+ MULTISET[ROW(ROW(x, y), p), ROW(ROW(z, w), q)] IS NOT A SET AS not_r
+FROM (VALUES
+ (1, 1, 'Foo', 10, 1, 'Foo', 10),
+ (2, 2, 'Bar', 10, 2, 'Car', 10),
+ (3, 3, 'Baz', 10, 4, 'Baz', 10),
+ (4, 5, NULL, 10, 5, NULL, 10),
+ (5, 6, 'Qux', 10, 6, 'Qux', 11)) AS t(id, x, y, p, z, w, q)
+ORDER BY id;
++----+-------+-------+
+| ID | R | NOT_R |
++----+-------+-------+
+| 1 | false | true |
+| 2 | true | false |
+| 3 | true | false |
+| 4 | false | true |
+| 5 | true | false |
++----+-------+-------+
+(5 rows)
+
+!ok
+
+# SUBMULTISET OF and NOT SUBMULTISET OF
+SELECT id,
+ MULTISET[ROW(ROW(x, y), p), ROW(ROW(x, y), p)]
+ SUBMULTISET OF MULTISET[ROW(ROW(z, w), q), ROW(ROW(z, w), q),
ROW(ROW(z, w), q), ROW(ROW(9, 'Zzz'), 10)] AS r,
+ MULTISET[ROW(ROW(x, y), p), ROW(ROW(x, y), p)]
+ NOT SUBMULTISET OF MULTISET[ROW(ROW(z, w), q), ROW(ROW(z, w), q),
ROW(ROW(z, w), q), ROW(ROW(9, 'Zzz'), 10)] AS not_r
+FROM (VALUES
+ (1, 1, 'Foo', 10, 1, 'Foo', 10),
+ (2, 2, 'Bar', 10, 2, 'Car', 10),
+ (3, 3, 'Baz', 10, 4, 'Baz', 10),
+ (4, 5, NULL, 10, 5, NULL, 10),
+ (5, 6, 'Qux', 10, 6, 'Qux', 11)) AS t(id, x, y, p, z, w, q)
+ORDER BY id;
++----+-------+-------+
+| ID | R | NOT_R |
++----+-------+-------+
+| 1 | true | false |
+| 2 | false | true |
+| 3 | false | true |
+| 4 | true | false |
+| 5 | false | true |
++----+-------+-------+
+(5 rows)
+
+!ok
+
+# Equal input cardinalities, but too few occurrences of the requested row.
+SELECT id, MULTISET[ROW(ROW(x, y), p), ROW(ROW(x, y), p)]
+ SUBMULTISET OF MULTISET[ROW(ROW(z, w), q), ROW(ROW(9, 'Zzz'), 10)] AS r
+FROM (VALUES
+ (1, 1, 'Foo', 10, 1, 'Foo', 10),
+ (2, 2, 'Bar', 10, 2, 'Car', 10),
+ (3, 3, 'Baz', 10, 4, 'Baz', 10),
+ (4, 5, NULL, 10, 5, NULL, 10),
+ (5, 6, 'Qux', 10, 6, 'Qux', 11)) AS t(id, x, y, p, z, w, q)
+ORDER BY id;
++----+-------+
+| ID | R |
++----+-------+
+| 1 | false |
+| 2 | false |
+| 3 | false |
+| 4 | false |
+| 5 | false |
++----+-------+
+(5 rows)
+
+!ok
+
+# MULTISET INTERSECT ALL
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(ROW(x, y), p), ROW(ROW(x, y), p), ROW(ROW(9, 'Zzz'), 10)]
+ MULTISET INTERSECT ALL
+ MULTISET[ROW(ROW(z, w), q), ROW(ROW(z, w), q), ROW(ROW(z, w), q),
ROW(ROW(8, 'Yyy'), 10)] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 10, 1, 'Foo', 10),
+ (2, 2, 'Bar', 10, 2, 'Car', 10),
+ (3, 3, 'Baz', 10, 4, 'Baz', 10),
+ (4, 5, NULL, 10, 5, NULL, 10),
+ (5, 6, 'Qux', 10, 6, 'Qux', 11)) AS t(id, x, y, p, z, w, q)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, u.a."EXPR$0", u.a."EXPR$1", b;
++----+-----------------+
+| ID | R |
++----+-----------------+
+| 1 | {{1, Foo}, 10} |
+| 1 | {{1, Foo}, 10} |
+| 4 | {{5, null}, 10} |
+| 4 | {{5, null}, 10} |
++----+-----------------+
+(4 rows)
+
+!ok
+
+# MULTISET INTERSECT DISTINCT
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(ROW(x, y), p), ROW(ROW(x, y), p), ROW(ROW(9, 'Zzz'), 10)]
+ MULTISET INTERSECT DISTINCT
+ MULTISET[ROW(ROW(z, w), q), ROW(ROW(z, w), q), ROW(ROW(z, w), q),
ROW(ROW(8, 'Yyy'), 10)] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 10, 1, 'Foo', 10),
+ (2, 2, 'Bar', 10, 2, 'Car', 10),
+ (3, 3, 'Baz', 10, 4, 'Baz', 10),
+ (4, 5, NULL, 10, 5, NULL, 10),
+ (5, 6, 'Qux', 10, 6, 'Qux', 11)) AS t(id, x, y, p, z, w, q)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, u.a."EXPR$0", u.a."EXPR$1", b;
++----+-----------------+
+| ID | R |
++----+-----------------+
+| 1 | {{1, Foo}, 10} |
+| 4 | {{5, null}, 10} |
++----+-----------------+
+(2 rows)
+
+!ok
+
+# MULTISET EXCEPT ALL
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(ROW(x, y), p), ROW(ROW(x, y), p), ROW(ROW(9, 'Zzz'), 10)]
+ MULTISET EXCEPT ALL
+ MULTISET[ROW(ROW(z, w), q), ROW(ROW(z, w), q), ROW(ROW(z, w), q),
ROW(ROW(8, 'Yyy'), 10)] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 10, 1, 'Foo', 10),
+ (2, 2, 'Bar', 10, 2, 'Car', 10),
+ (3, 3, 'Baz', 10, 4, 'Baz', 10),
+ (4, 5, NULL, 10, 5, NULL, 10),
+ (5, 6, 'Qux', 10, 6, 'Qux', 11)) AS t(id, x, y, p, z, w, q)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, u.a."EXPR$0", u.a."EXPR$1", b;
++----+----------------+
+| ID | R |
++----+----------------+
+| 1 | {{9, Zzz}, 10} |
+| 2 | {{2, Bar}, 10} |
+| 2 | {{2, Bar}, 10} |
+| 2 | {{9, Zzz}, 10} |
+| 3 | {{3, Baz}, 10} |
+| 3 | {{3, Baz}, 10} |
+| 3 | {{9, Zzz}, 10} |
+| 4 | {{9, Zzz}, 10} |
+| 5 | {{6, Qux}, 10} |
+| 5 | {{6, Qux}, 10} |
+| 5 | {{9, Zzz}, 10} |
++----+----------------+
+(11 rows)
+
+!ok
+
+# MULTISET EXCEPT DISTINCT
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(ROW(x, y), p), ROW(ROW(x, y), p), ROW(ROW(9, 'Zzz'), 10)]
+ MULTISET EXCEPT DISTINCT
+ MULTISET[ROW(ROW(z, w), q), ROW(ROW(z, w), q), ROW(ROW(z, w), q),
ROW(ROW(8, 'Yyy'), 10)] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 10, 1, 'Foo', 10),
+ (2, 2, 'Bar', 10, 2, 'Car', 10),
+ (3, 3, 'Baz', 10, 4, 'Baz', 10),
+ (4, 5, NULL, 10, 5, NULL, 10),
+ (5, 6, 'Qux', 10, 6, 'Qux', 11)) AS t(id, x, y, p, z, w, q)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, u.a."EXPR$0", u.a."EXPR$1", b;
++----+----------------+
+| ID | R |
++----+----------------+
+| 1 | {{9, Zzz}, 10} |
+| 2 | {{2, Bar}, 10} |
+| 2 | {{9, Zzz}, 10} |
+| 3 | {{3, Baz}, 10} |
+| 3 | {{9, Zzz}, 10} |
+| 4 | {{9, Zzz}, 10} |
+| 5 | {{6, Qux}, 10} |
+| 5 | {{9, Zzz}, 10} |
++----+----------------+
+(8 rows)
+
+!ok
+
+# MULTISET UNION DISTINCT
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(ROW(x, y), p), ROW(ROW(x, y), p), ROW(ROW(9, 'Zzz'), 10)]
+ MULTISET UNION DISTINCT
+ MULTISET[ROW(ROW(z, w), q), ROW(ROW(z, w), q), ROW(ROW(z, w), q),
ROW(ROW(8, 'Yyy'), 10)] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 10, 1, 'Foo', 10),
+ (2, 2, 'Bar', 10, 2, 'Car', 10),
+ (3, 3, 'Baz', 10, 4, 'Baz', 10),
+ (4, 5, NULL, 10, 5, NULL, 10),
+ (5, 6, 'Qux', 10, 6, 'Qux', 11)) AS t(id, x, y, p, z, w, q)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, u.a."EXPR$0", u.a."EXPR$1", b;
++----+-----------------+
+| ID | R |
++----+-----------------+
+| 1 | {{1, Foo}, 10} |
+| 1 | {{8, Yyy}, 10} |
+| 1 | {{9, Zzz}, 10} |
+| 2 | {{2, Bar}, 10} |
+| 2 | {{2, Car}, 10} |
+| 2 | {{8, Yyy}, 10} |
+| 2 | {{9, Zzz}, 10} |
+| 3 | {{3, Baz}, 10} |
+| 3 | {{4, Baz}, 10} |
+| 3 | {{8, Yyy}, 10} |
+| 3 | {{9, Zzz}, 10} |
+| 4 | {{5, null}, 10} |
+| 4 | {{8, Yyy}, 10} |
+| 4 | {{9, Zzz}, 10} |
+| 5 | {{6, Qux}, 10} |
+| 5 | {{6, Qux}, 11} |
+| 5 | {{8, Yyy}, 10} |
+| 5 | {{9, Zzz}, 10} |
++----+-----------------+
+(18 rows)
+
+!ok
+
+# MULTISET UNION ALL
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(ROW(x, y), p), ROW(ROW(x, y), p), ROW(ROW(9, 'Zzz'), 10)]
+ MULTISET UNION ALL
+ MULTISET[ROW(ROW(z, w), q), ROW(ROW(z, w), q), ROW(ROW(z, w), q),
ROW(ROW(8, 'Yyy'), 10)] AS m
+ FROM (VALUES
+ (1, 1, 'Foo', 10, 1, 'Foo', 10),
+ (2, 2, 'Bar', 10, 2, 'Car', 10),
+ (3, 3, 'Baz', 10, 4, 'Baz', 10),
+ (4, 5, NULL, 10, 5, NULL, 10),
+ (5, 6, 'Qux', 10, 6, 'Qux', 11)) AS t(id, x, y, p, z, w, q)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, u.a."EXPR$0", u.a."EXPR$1", b;
++----+-----------------+
+| ID | R |
++----+-----------------+
+| 1 | {{1, Foo}, 10} |
+| 1 | {{1, Foo}, 10} |
+| 1 | {{1, Foo}, 10} |
+| 1 | {{1, Foo}, 10} |
+| 1 | {{1, Foo}, 10} |
+| 1 | {{8, Yyy}, 10} |
+| 1 | {{9, Zzz}, 10} |
+| 2 | {{2, Bar}, 10} |
+| 2 | {{2, Bar}, 10} |
+| 2 | {{2, Car}, 10} |
+| 2 | {{2, Car}, 10} |
+| 2 | {{2, Car}, 10} |
+| 2 | {{8, Yyy}, 10} |
+| 2 | {{9, Zzz}, 10} |
+| 3 | {{3, Baz}, 10} |
+| 3 | {{3, Baz}, 10} |
+| 3 | {{4, Baz}, 10} |
+| 3 | {{4, Baz}, 10} |
+| 3 | {{4, Baz}, 10} |
+| 3 | {{8, Yyy}, 10} |
+| 3 | {{9, Zzz}, 10} |
+| 4 | {{5, null}, 10} |
+| 4 | {{5, null}, 10} |
+| 4 | {{5, null}, 10} |
+| 4 | {{5, null}, 10} |
+| 4 | {{5, null}, 10} |
+| 4 | {{8, Yyy}, 10} |
+| 4 | {{9, Zzz}, 10} |
+| 5 | {{6, Qux}, 10} |
+| 5 | {{6, Qux}, 10} |
+| 5 | {{6, Qux}, 11} |
+| 5 | {{6, Qux}, 11} |
+| 5 | {{6, Qux}, 11} |
+| 5 | {{8, Yyy}, 10} |
+| 5 | {{9, Zzz}, 10} |
++----+-----------------+
+(35 rows)
+
+!ok
+
+# ROW values with NULL fields.
+
+# MEMBER OF
+SELECT id, ROW(x, CAST(NULL AS INTEGER)) MEMBER OF MULTISET[ROW(z, CAST(NULL
AS INTEGER))] AS r
+FROM (VALUES
+ (1, 1, 1),
+ (2, 2, 3)) AS t(id, x, z)
+ORDER BY id;
++----+-------+
+| ID | R |
++----+-------+
+| 1 | true |
+| 2 | false |
++----+-------+
+(2 rows)
+
+!ok
+
+# IS A SET and IS NOT A SET
+SELECT id,
+ MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(z, CAST(NULL AS INTEGER))] IS
A SET AS r,
+ MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(z, CAST(NULL AS INTEGER))] IS
NOT A SET AS not_r
+FROM (VALUES
+ (1, 1, 1),
+ (2, 2, 3)) AS t(id, x, z)
+ORDER BY id;
++----+-------+-------+
+| ID | R | NOT_R |
++----+-------+-------+
+| 1 | false | true |
+| 2 | true | false |
++----+-------+-------+
+(2 rows)
+
+!ok
+
+# SUBMULTISET OF and NOT SUBMULTISET OF
+SELECT id,
+ MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(x, CAST(NULL AS INTEGER))]
+ SUBMULTISET OF MULTISET[ROW(z, CAST(NULL AS INTEGER)), ROW(z,
CAST(NULL AS INTEGER)), ROW(z, CAST(NULL AS INTEGER)), ROW(9, CAST(NULL AS
INTEGER))] AS r,
+ MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(x, CAST(NULL AS INTEGER))]
+ NOT SUBMULTISET OF MULTISET[ROW(z, CAST(NULL AS INTEGER)), ROW(z,
CAST(NULL AS INTEGER)), ROW(z, CAST(NULL AS INTEGER)), ROW(9, CAST(NULL AS
INTEGER))] AS not_r
+FROM (VALUES
+ (1, 1, 1),
+ (2, 2, 3)) AS t(id, x, z)
+ORDER BY id;
++----+-------+-------+
+| ID | R | NOT_R |
++----+-------+-------+
+| 1 | true | false |
+| 2 | false | true |
++----+-------+-------+
+(2 rows)
+
+!ok
+
+# Equal input cardinalities, but too few occurrences of the requested row.
+SELECT id, MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(x, CAST(NULL AS
INTEGER))]
+ SUBMULTISET OF MULTISET[ROW(z, CAST(NULL AS INTEGER)), ROW(9, CAST(NULL AS
INTEGER))] AS r
+FROM (VALUES
+ (1, 1, 1),
+ (2, 2, 3)) AS t(id, x, z)
+ORDER BY id;
++----+-------+
+| ID | R |
++----+-------+
+| 1 | false |
+| 2 | false |
++----+-------+
+(2 rows)
+
+!ok
+
+# MULTISET INTERSECT ALL
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(x, CAST(NULL AS INTEGER)),
ROW(9, CAST(NULL AS INTEGER))]
+ MULTISET INTERSECT ALL
+ MULTISET[ROW(z, CAST(NULL AS INTEGER)), ROW(z, CAST(NULL AS INTEGER)),
ROW(z, CAST(NULL AS INTEGER)), ROW(8, CAST(NULL AS INTEGER))] AS m
+ FROM (VALUES
+ (1, 1, 1),
+ (2, 2, 3)) AS t(id, x, z)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+-----------+
+| ID | R |
++----+-----------+
+| 1 | {1, null} |
+| 1 | {1, null} |
++----+-----------+
+(2 rows)
+
+!ok
+
+# MULTISET INTERSECT DISTINCT
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(x, CAST(NULL AS INTEGER)),
ROW(9, CAST(NULL AS INTEGER))]
+ MULTISET INTERSECT DISTINCT
+ MULTISET[ROW(z, CAST(NULL AS INTEGER)), ROW(z, CAST(NULL AS INTEGER)),
ROW(z, CAST(NULL AS INTEGER)), ROW(8, CAST(NULL AS INTEGER))] AS m
+ FROM (VALUES
+ (1, 1, 1),
+ (2, 2, 3)) AS t(id, x, z)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+-----------+
+| ID | R |
++----+-----------+
+| 1 | {1, null} |
++----+-----------+
+(1 row)
+
+!ok
+
+# MULTISET EXCEPT ALL
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(x, CAST(NULL AS INTEGER)),
ROW(9, CAST(NULL AS INTEGER))]
+ MULTISET EXCEPT ALL
+ MULTISET[ROW(z, CAST(NULL AS INTEGER)), ROW(z, CAST(NULL AS INTEGER)),
ROW(z, CAST(NULL AS INTEGER)), ROW(8, CAST(NULL AS INTEGER))] AS m
+ FROM (VALUES
+ (1, 1, 1),
+ (2, 2, 3)) AS t(id, x, z)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+-----------+
+| ID | R |
++----+-----------+
+| 1 | {9, null} |
+| 2 | {2, null} |
+| 2 | {2, null} |
+| 2 | {9, null} |
++----+-----------+
+(4 rows)
+
+!ok
+
+# MULTISET EXCEPT DISTINCT
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(x, CAST(NULL AS INTEGER)),
ROW(9, CAST(NULL AS INTEGER))]
+ MULTISET EXCEPT DISTINCT
+ MULTISET[ROW(z, CAST(NULL AS INTEGER)), ROW(z, CAST(NULL AS INTEGER)),
ROW(z, CAST(NULL AS INTEGER)), ROW(8, CAST(NULL AS INTEGER))] AS m
+ FROM (VALUES
+ (1, 1, 1),
+ (2, 2, 3)) AS t(id, x, z)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+-----------+
+| ID | R |
++----+-----------+
+| 1 | {9, null} |
+| 2 | {2, null} |
+| 2 | {9, null} |
++----+-----------+
+(3 rows)
+
+!ok
+
+# MULTISET UNION DISTINCT
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(x, CAST(NULL AS INTEGER)),
ROW(9, CAST(NULL AS INTEGER))]
+ MULTISET UNION DISTINCT
+ MULTISET[ROW(z, CAST(NULL AS INTEGER)), ROW(z, CAST(NULL AS INTEGER)),
ROW(z, CAST(NULL AS INTEGER)), ROW(8, CAST(NULL AS INTEGER))] AS m
+ FROM (VALUES
+ (1, 1, 1),
+ (2, 2, 3)) AS t(id, x, z)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+-----------+
+| ID | R |
++----+-----------+
+| 1 | {1, null} |
+| 1 | {8, null} |
+| 1 | {9, null} |
+| 2 | {2, null} |
+| 2 | {3, null} |
+| 2 | {8, null} |
+| 2 | {9, null} |
++----+-----------+
+(7 rows)
+
+!ok
+
+# MULTISET UNION ALL
+SELECT id, ROW(a, b) AS r
+FROM (
+ SELECT id,
+ MULTISET[ROW(x, CAST(NULL AS INTEGER)), ROW(x, CAST(NULL AS INTEGER)),
ROW(9, CAST(NULL AS INTEGER))]
+ MULTISET UNION ALL
+ MULTISET[ROW(z, CAST(NULL AS INTEGER)), ROW(z, CAST(NULL AS INTEGER)),
ROW(z, CAST(NULL AS INTEGER)), ROW(8, CAST(NULL AS INTEGER))] AS m
+ FROM (VALUES
+ (1, 1, 1),
+ (2, 2, 3)) AS t(id, x, z)) AS t
+CROSS JOIN UNNEST(m) AS u(a, b)
+ORDER BY id, a, b;
++----+-----------+
+| ID | R |
++----+-----------+
+| 1 | {1, null} |
+| 1 | {1, null} |
+| 1 | {1, null} |
+| 1 | {1, null} |
+| 1 | {1, null} |
+| 1 | {8, null} |
+| 1 | {9, null} |
+| 2 | {2, null} |
+| 2 | {2, null} |
+| 2 | {3, null} |
+| 2 | {3, null} |
+| 2 | {3, null} |
+| 2 | {8, null} |
+| 2 | {9, null} |
++----+-----------+
+(14 rows)
+
+!ok
+
# End row-equality.iq