From e7b8ad7ed9653d237e09dee89ba419585717a789 Mon Sep 17 00:00:00 2001
From: shihao zhong <zhong950419@gmail.com>
Date: Tue, 3 Nov 2026 21:50:41 -0500
Subject: [PATCH v1 2/2] Convert pathkeys of nested set operations instead of
 sharing classes

A child set operation keeps its own pathkeys. The parent converts them
to its output columns, like convert_subquery_pathkeys().

Discussion: https://postgr.es/m/1087303.1791221946@sss.pgh.pa.us
---
 src/backend/optimizer/path/equivclass.c | 164 +++++++----------------
 src/backend/optimizer/prep/prepunion.c  | 166 ++++++++++++++++++++----
 src/include/optimizer/paths.h           |   1 -
 src/test/regress/expected/union.out     |  51 ++++++++
 src/test/regress/sql/union.sql          |  11 ++
 5 files changed, 250 insertions(+), 143 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 9888ad89322..ddcfbcd5ba6 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -30,6 +30,7 @@
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
 
@@ -747,6 +748,7 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	Relids		expr_relids;
 	EquivalenceClass *newec;
 	EquivalenceMember *newem;
+	Index		setop_varno = 0;
 	ListCell   *lc1;
 	MemoryContext oldcontext;
 
@@ -761,6 +763,26 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	 */
 	jdomain = linitial_node(JoinDomain, root->join_domains);
 
+	/*
+	 * A Var of an RTE_SETOP entry can only match a parent member of an EC
+	 * whose ec_relids include its varno, since parent members get their
+	 * relids from pull_varnos and ec_relids never shrink.  Checking that
+	 * first is much cheaper than comparing opfamilies and members.
+	 */
+	if (relids == NULL)
+	{
+		Expr	   *e = expr;
+
+		while (IsA(e, RelabelType))
+			e = ((RelabelType *) e)->arg;
+		if (IsA(e, Var) &&
+			((Var *) e)->varlevelsup == 0 &&
+			((Var *) e)->varnullingrels == NULL &&
+			!IS_SPECIAL_VARNO(((Var *) e)->varno) &&
+			planner_rt_fetch(((Var *) e)->varno, root)->rtekind == RTE_SETOP)
+			setop_varno = ((Var *) e)->varno;
+	}
+
 	/*
 	 * Scan through the existing EquivalenceClasses for a match
 	 */
@@ -770,6 +792,9 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 		EquivalenceMemberIterator it;
 		EquivalenceMember *cur_em;
 
+		if (setop_varno > 0 && !bms_is_member(setop_varno, cur_ec->ec_relids))
+			continue;
+
 		/*
 		 * Never match to a volatile EC, except when we are looking at another
 		 * reference to the same volatile SortGroupClause.
@@ -3149,7 +3174,8 @@ add_child_rel_pathkey_equivalences(PlannerInfo *root, RelOptInfo *child_rel,
  *
  * 'root' is the PlannerInfo belonging to the top-level set operation.
  * 'child_rel' is the RelOptInfo of the child relation we're adding
- * EquivalenceMembers for.
+ * EquivalenceMembers for.  This is either a subquery's base relation or the
+ * upper relation of a set operation nested below the one being planned.
  * 'child_tlist' is the target list for the setop child relation.  The target
  * list expressions are what we add as EquivalenceMembers.
  * 'setop_pathkeys' is a list of PathKeys which must contain an entry for each
@@ -3161,6 +3187,16 @@ add_setop_child_rel_equivalences(PlannerInfo *root, RelOptInfo *child_rel,
 {
 	ListCell   *lc;
 	ListCell   *lc2 = list_head(setop_pathkeys);
+	Index		child_relid;
+
+	/*
+	 * A nested set operation has no relid of its own, so store its members
+	 * under the lowest relid in its relids, as is done for child joinrels.
+	 */
+	if (child_rel->reloptkind == RELOPT_BASEREL)
+		child_relid = child_rel->relid;
+	else
+		child_relid = bms_next_member(child_rel->relids, -1);
 
 	foreach(lc, child_tlist)
 	{
@@ -3183,6 +3219,11 @@ add_setop_child_rel_equivalences(PlannerInfo *root, RelOptInfo *child_rel,
 		 * likewise, the JoinDomain can be that of the initial member of the
 		 * Pathkey's EquivalenceClass.  We pass -1 for ec_index since we
 		 * maintain the eclass_indexes for the child_rel after the loop.
+		 *
+		 * The child member takes its parent's em_datatype, not the type of
+		 * the child expression.  The two differ when the column type is only
+		 * binary-compatible with the opclass input type, such as varchar with
+		 * text_ops, and a sort operator can only be looked up by the latter.
 		 */
 		add_child_eq_member(root,
 							pk->pk_eclass,
@@ -3191,8 +3232,8 @@ add_setop_child_rel_equivalences(PlannerInfo *root, RelOptInfo *child_rel,
 							child_rel->relids,
 							parent_em->em_jdomain,
 							parent_em,
-							exprType((Node *) tle->expr),
-							child_rel->relid);
+							parent_em->em_datatype,
+							child_relid);
 
 		lc2 = lnext(setop_pathkeys, lc2);
 	}
@@ -3207,123 +3248,6 @@ add_setop_child_rel_equivalences(PlannerInfo *root, RelOptInfo *child_rel,
 											  list_length(root->eq_classes) - 1);
 }
 
-/*
- * add_setop_output_equivalences
- *		Add the output Vars of a set operation to the EquivalenceClasses that
- *		already contain the same output column of another set operation in
- *		this query level.
- *
- * Nested set operations are planned within one PlannerInfo.  A parent can
- * only make use of a child set operation's sorted paths if both refer to
- * their output columns through the same EquivalenceClasses, so whenever a
- * Var in 'tlist' matches the RTE_SETOP Var of another set operation's output
- * column (same column number, type, typmod and collation), we add it to each
- * EquivalenceClass containing that Var.  This must be done before building
- * any pathkeys from 'tlist'.  Entries of 'tlist' that are not RTE_SETOP Vars
- * are ignored.
- */
-void
-add_setop_output_equivalences(PlannerInfo *root, List *tlist)
-{
-	int			ncols = list_length(tlist);
-	Var		  **vars;
-	EquivalenceMember **match;
-	bool	   *present;
-	bool		found = false;
-
-	/* Index our output Vars by column number */
-	vars = palloc0_array(Var *, ncols + 1);
-	foreach_node(TargetEntry, tle, tlist)
-	{
-		Var		   *var = (Var *) tle->expr;
-
-		/* Only output columns of set operations are of interest */
-		if (!IsA(var, Var) || var->varlevelsup != 0 ||
-			var->varattno < 1 || var->varattno > ncols ||
-			root->simple_rte_array[var->varno]->rtekind != RTE_SETOP)
-			continue;
-		/* Callers build 'tlist' so that each column number appears once */
-		Assert(vars[var->varattno] == NULL);
-		vars[var->varattno] = var;
-		found = true;
-	}
-	if (!found)
-		return;
-
-	match = palloc0_array(EquivalenceMember *, ncols + 1);
-	present = palloc0_array(bool, ncols + 1);
-
-	foreach_node(EquivalenceClass, ec, root->eq_classes)
-	{
-		List	   *seen = NIL;
-
-		if (ec->ec_has_volatile)
-			continue;
-
-		/*
-		 * For each of our columns, find the first member that is the same
-		 * output column of another set operation, and note whether our own
-		 * Var is already a member.
-		 */
-		foreach_node(EquivalenceMember, em, ec->ec_members)
-		{
-			Expr	   *expr = em->em_expr;
-			Var		   *emvar;
-			Var		   *var;
-
-			/* Look through any RelabelType added by canonicalization */
-			while (IsA(expr, RelabelType))
-				expr = ((RelabelType *) expr)->arg;
-			if (!IsA(expr, Var))
-				continue;
-			emvar = (Var *) expr;
-			if (emvar->varlevelsup != 0 ||
-				emvar->varattno < 1 || emvar->varattno > ncols)
-				continue;
-			var = vars[emvar->varattno];
-			if (var == NULL ||
-				root->simple_rte_array[emvar->varno]->rtekind != RTE_SETOP ||
-				emvar->vartype != var->vartype ||
-				emvar->vartypmod != var->vartypmod ||
-				emvar->varcollid != var->varcollid)
-				continue;
-
-			if (match[var->varattno] == NULL && !present[var->varattno])
-				seen = lappend_int(seen, var->varattno);
-			if (emvar->varno == var->varno)
-				present[var->varattno] = true;
-			else if (match[var->varattno] == NULL)
-				match[var->varattno] = em;
-		}
-
-		/* Add a copy of each match that uses our varno, then reset */
-		foreach_int(attno, seen)
-		{
-			if (match[attno] != NULL && !present[attno])
-			{
-				Expr	   *newexpr = copyObject(match[attno]->em_expr);
-				Expr	   *expr = newexpr;
-
-				while (IsA(expr, RelabelType))
-					expr = ((RelabelType *) expr)->arg;
-				((Var *) expr)->varno = vars[attno]->varno;
-
-				add_eq_member(ec, newexpr,
-							  bms_make_singleton(vars[attno]->varno),
-							  match[attno]->em_jdomain,
-							  match[attno]->em_datatype);
-			}
-			match[attno] = NULL;
-			present[attno] = false;
-		}
-		list_free(seen);
-	}
-
-	pfree(vars);
-	pfree(match);
-	pfree(present);
-}
-
 /*
  * setup_eclass_member_iterator
  *	  Setup an EquivalenceMemberIterator 'it' to iterate over all parent
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 3d0a5e25303..45a97fd1e46 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -685,6 +685,128 @@ build_setop_child_paths(PlannerInfo *root, RelOptInfo *rel,
 	}
 }
 
+/*
+ * convert_setop_child_path
+ *		Return a path of a nested set operation, with its pathkeys
+ *		converted to refer to the output columns of the parent set operation.
+ *
+ * The paths of a set operation nested below the one being planned are
+ * sorted by the child's output Vars, which the parent knows nothing about.
+ * Column k of the parent has the same value as column k of the child if the
+ * child's column is a plain output Var of the same type, typmod and
+ * collation, and in that case a pathkey for the child's column can be
+ * converted to the parent's.  We stop at the first pathkey that cannot be
+ * converted.
+ *
+ * 'child_tlist' is the child's target list as seen by the parent, and
+ * 'parent_tlist' is the parent's.  The path is wrapped in a
+ * ProjectionPath that computes the same target, so it costs the same as
+ * the original and does not add a node to the plan.
+ */
+static Path *
+convert_setop_child_path(PlannerInfo *root, Path *path,
+						 List *child_tlist, List *parent_tlist)
+{
+	List	   *pathkeys = NIL;
+	ProjectionPath *ppath;
+
+	Assert(list_length(child_tlist) == list_length(parent_tlist));
+
+	foreach_node(PathKey, pk, path->pathkeys)
+	{
+		EquivalenceClass *ec = pk->pk_eclass;
+		EquivalenceClass *parent_ec = NULL;
+
+		foreach_node(EquivalenceMember, em, ec->ec_members)
+		{
+			Expr	   *expr = em->em_expr;
+			Var		   *var;
+			Var		   *parent_var;
+
+			/* Look through any RelabelType added by canonicalization */
+			while (IsA(expr, RelabelType))
+				expr = ((RelabelType *) expr)->arg;
+			if (!IsA(expr, Var))
+				continue;
+			var = (Var *) expr;
+			if (var->varlevelsup != 0 || var->varattno < 1 ||
+				var->varattno > list_length(child_tlist))
+				continue;
+
+			/* It must be the child's output column ... */
+			if (!equal(list_nth_node(TargetEntry, child_tlist,
+									 var->varattno - 1)->expr, var))
+				continue;
+
+			/* ... with the same type, typmod and collation as ours */
+			parent_var = (Var *) list_nth_node(TargetEntry, parent_tlist,
+											   var->varattno - 1)->expr;
+			if (!IsA(parent_var, Var) ||
+				parent_var->vartype != var->vartype ||
+				parent_var->vartypmod != var->vartypmod ||
+				parent_var->varcollid != var->varcollid)
+				continue;
+
+			parent_ec = get_eclass_for_sort_expr(root, (Expr *) parent_var,
+												 ec->ec_opfamilies,
+												 em->em_datatype,
+												 ec->ec_collation,
+												 0, NULL, false);
+			if (parent_ec != NULL)
+				break;
+		}
+
+		if (parent_ec == NULL)
+			break;
+
+		pathkeys = lappend(pathkeys,
+						   make_canonical_pathkey(root, parent_ec,
+												  pk->pk_opfamily,
+												  pk->pk_cmptype,
+												  pk->pk_nulls_first));
+	}
+
+	ppath = create_projection_path(root, path->parent, path, path->pathtarget);
+	ppath->path.pathkeys = pathkeys;
+
+	return (Path *) ppath;
+}
+
+/*
+ * get_setop_child_sorted_path
+ *		Find the cheapest path of a nested set operation 'rel' that is sorted
+ *		by the parent's 'pathkeys', or return NULL if there is none.
+ *
+ * The result is converted by convert_setop_child_path().  We also add the
+ * child's output columns as child members of the parent's
+ * EquivalenceClasses, so that createplan.c can find the sort columns in the
+ * child's target list.
+ */
+static Path *
+get_setop_child_sorted_path(PlannerInfo *root, RelOptInfo *rel,
+							List *child_tlist, List *parent_tlist,
+							List *pathkeys)
+{
+	List	   *paths = NIL;
+	Path	   *result;
+
+	foreach_ptr(Path, path, rel->pathlist)
+	{
+		if (path->pathkeys != NIL)
+			paths = lappend(paths, convert_setop_child_path(root, path,
+															child_tlist,
+															parent_tlist));
+	}
+
+	result = get_cheapest_path_for_pathkeys(paths, pathkeys, NULL,
+											TOTAL_COST, false);
+	if (result != NULL)
+		add_setop_child_rel_equivalences(root, rel, child_tlist, pathkeys);
+
+	list_free(paths);
+	return result;
+}
+
 /*
  * Generate paths for a UNION or UNION ALL node
  */
@@ -738,9 +860,6 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
 								  tlist_list, refnames_tlist);
 	*pTargetList = tlist;
 
-	/* Share pathkeys with any set operations nested below this one */
-	add_setop_output_equivalences(root, tlist);
-
 	/* For UNIONs (not UNION ALL), try sorting, if sorting is possible */
 	if (!op->all)
 	{
@@ -790,9 +909,10 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
 	}
 
 	/* Build path lists and relid set. */
-	foreach(lc, rellist)
+	forboth(lc, rellist, lc3, tlist_list)
 	{
 		RelOptInfo *rel = lfirst(lc);
+		List	   *child_tlist = lfirst_node(List, lc3);
 		Path	   *ordered_path;
 
 		/*
@@ -810,11 +930,16 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
 
 		if (try_sorted)
 		{
-			ordered_path = get_cheapest_path_for_pathkeys(rel->pathlist,
-														  union_pathkeys,
-														  NULL,
-														  TOTAL_COST,
-														  false);
+			if (rel->rtekind == RTE_SUBQUERY)
+				ordered_path = get_cheapest_path_for_pathkeys(rel->pathlist,
+															  union_pathkeys,
+															  NULL,
+															  TOTAL_COST,
+															  false);
+			else
+				ordered_path = get_setop_child_sorted_path(root, rel,
+														   child_tlist, tlist,
+														   union_pathkeys);
 
 			if (ordered_path != NULL)
 				ordered.subpaths = lappend(ordered.subpaths, ordered_path);
@@ -1124,9 +1249,6 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
 
 	*pTargetList = tlist;
 
-	/* Share pathkeys with any set operations nested below this one */
-	add_setop_output_equivalences(root, tlist);
-
 	/* Identify the grouping semantics */
 	groupList = generate_setop_grouplist(op, tlist);
 
@@ -1148,14 +1270,6 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
 														  tlist);
 
 		root->query_pathkeys = nonunion_pathkeys;
-
-		/*
-		 * A child set operation that built no pathkeys of its own (such as
-		 * UNION ALL) has not been linked to our EquivalenceClasses yet.  Do
-		 * it now, so that sorting a child's output yields our pathkeys.
-		 */
-		add_setop_output_equivalences(root, lpath_tlist);
-		add_setop_output_equivalences(root, rpath_tlist);
 	}
 
 	/*
@@ -1359,8 +1473,10 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
 			slpath = lpath;		/* cheapest path is already sorted */
 		else
 		{
+			/* A nested set operation's paths are sorted by its own columns */
 			slpath = get_cheapest_path_for_pathkeys(lrel->pathlist,
-													nonunion_pathkeys,
+													lrel->rtekind == RTE_SUBQUERY ?
+													nonunion_pathkeys : pathkeys,
 													NULL,
 													TOTAL_COST,
 													false);
@@ -1372,6 +1488,8 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
 												   pathkeys,
 												   -1.0);
 		}
+		if (lrel->rtekind != RTE_SUBQUERY)
+			slpath = convert_setop_child_path(root, slpath, lpath_tlist, tlist);
 
 		/* and now the same for the right. */
 		pathkeys = make_pathkeys_for_sortclauses(root,
@@ -1381,8 +1499,10 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
 			srpath = rpath;		/* cheapest path is already sorted */
 		else
 		{
+			/* A nested set operation's paths are sorted by its own columns */
 			srpath = get_cheapest_path_for_pathkeys(rrel->pathlist,
-													nonunion_pathkeys,
+													rrel->rtekind == RTE_SUBQUERY ?
+													nonunion_pathkeys : pathkeys,
 													NULL,
 													TOTAL_COST,
 													false);
@@ -1394,6 +1514,8 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
 												   pathkeys,
 												   -1.0);
 		}
+		if (rrel->rtekind != RTE_SUBQUERY)
+			srpath = convert_setop_child_path(root, srpath, rpath_tlist, tlist);
 
 		path = (Path *) create_setop_path(root,
 										  result_rel,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index eac439578c6..64f87da1674 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -191,7 +191,6 @@ extern void add_setop_child_rel_equivalences(PlannerInfo *root,
 											 RelOptInfo *child_rel,
 											 List *child_tlist,
 											 List *setop_pathkeys);
-extern void add_setop_output_equivalences(PlannerInfo *root, List *tlist);
 extern void setup_eclass_member_iterator(EquivalenceMemberIterator *it,
 										 EquivalenceClass *ec,
 										 Relids child_relids);
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 9cffe854b19..8cbca3eae2f 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -1598,6 +1598,57 @@ ORDER BY 1;
          Index Cond: (unique1 < 5)
 (8 rows)
 
+-- Same, with a type that is only binary-compatible with the input type of
+-- its sort opclass.  The Merge Append must still find a sort operator.
+EXPLAIN (COSTS OFF)
+(SELECT stringu1::varchar FROM tenk1 WHERE unique1 < 3
+ UNION SELECT stringu2::varchar FROM tenk1 WHERE unique1 < 3
+ EXCEPT SELECT stringu1::varchar FROM tenk1 WHERE unique1 = 0)
+UNION
+(SELECT stringu1::varchar FROM tenk1 WHERE unique1 BETWEEN 5 AND 6
+ UNION SELECT stringu2::varchar FROM tenk1 WHERE unique1 BETWEEN 5 AND 6
+ EXCEPT SELECT stringu2::varchar FROM tenk1 WHERE unique1 = 5);
+                                         QUERY PLAN                                          
+---------------------------------------------------------------------------------------------
+ Unique
+   ->  Merge Append
+         Sort Key: ((tenk1.stringu1)::character varying) COLLATE "C"
+         ->  SetOp Except
+               ->  Unique
+                     ->  Sort
+                           Sort Key: ((tenk1.stringu1)::character varying) COLLATE "C"
+                           ->  Append
+                                 ->  Bitmap Heap Scan on tenk1
+                                       Recheck Cond: (unique1 < 3)
+                                       ->  Bitmap Index Scan on tenk1_unique1
+                                             Index Cond: (unique1 < 3)
+                                 ->  Bitmap Heap Scan on tenk1 tenk1_1
+                                       Recheck Cond: (unique1 < 3)
+                                       ->  Bitmap Index Scan on tenk1_unique1
+                                             Index Cond: (unique1 < 3)
+               ->  Sort
+                     Sort Key: ((tenk1_2.stringu1)::character varying) COLLATE "C"
+                     ->  Index Scan using tenk1_unique1 on tenk1 tenk1_2
+                           Index Cond: (unique1 = 0)
+         ->  SetOp Except
+               ->  Unique
+                     ->  Sort
+                           Sort Key: ((tenk1_3.stringu1)::character varying) COLLATE "C"
+                           ->  Append
+                                 ->  Bitmap Heap Scan on tenk1 tenk1_3
+                                       Recheck Cond: ((unique1 >= 5) AND (unique1 <= 6))
+                                       ->  Bitmap Index Scan on tenk1_unique1
+                                             Index Cond: ((unique1 >= 5) AND (unique1 <= 6))
+                                 ->  Bitmap Heap Scan on tenk1 tenk1_4
+                                       Recheck Cond: ((unique1 >= 5) AND (unique1 <= 6))
+                                       ->  Bitmap Index Scan on tenk1_unique1
+                                             Index Cond: ((unique1 >= 5) AND (unique1 <= 6))
+               ->  Sort
+                     Sort Key: ((tenk1_5.stringu2)::character varying) COLLATE "C"
+                     ->  Index Scan using tenk1_unique1 on tenk1 tenk1_5
+                           Index Cond: (unique1 = 5)
+(37 rows)
+
 RESET enable_hashagg;
 -- Test constraint exclusion of UNION ALL subqueries
 explain (costs off)
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 59e376b73a4..6b1aa18b075 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -611,6 +611,17 @@ EXPLAIN (COSTS OFF)
 EXCEPT
 SELECT unique1 FROM tenk1 WHERE unique1 < 5
 ORDER BY 1;
+
+-- Same, with a type that is only binary-compatible with the input type of
+-- its sort opclass.  The Merge Append must still find a sort operator.
+EXPLAIN (COSTS OFF)
+(SELECT stringu1::varchar FROM tenk1 WHERE unique1 < 3
+ UNION SELECT stringu2::varchar FROM tenk1 WHERE unique1 < 3
+ EXCEPT SELECT stringu1::varchar FROM tenk1 WHERE unique1 = 0)
+UNION
+(SELECT stringu1::varchar FROM tenk1 WHERE unique1 BETWEEN 5 AND 6
+ UNION SELECT stringu2::varchar FROM tenk1 WHERE unique1 BETWEEN 5 AND 6
+ EXCEPT SELECT stringu2::varchar FROM tenk1 WHERE unique1 = 5);
 RESET enable_hashagg;
 
 -- Test constraint exclusion of UNION ALL subqueries
-- 
2.37.1 (Apple Git-137.1)

