From 21634e391305a6e5f029068af62ebf6baf450037 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Mon, 7 Dec 2020 14:59:34 +0000
Subject: [PATCH 2/2] Improve estimation of ANDs under ORs using extended
 statistics.

Formerly, extended statistics only handled clauses that were
RestrictInfos. However, the restrictinfo machinery doesn't create
sub-AND RestrictInfos for AND clauses underneath OR clauses.
Therefore teach extended statistics to handle bare AND clauses,
looking for compatible RestrictInfo clauses underneath them.
---
 src/backend/optimizer/path/clausesel.c  | 21 +++++++++++++++++++++
 src/backend/statistics/extended_stats.c | 25 +++++++++++++++++++++++++
 src/test/regress/expected/stats_ext.out |  4 ++--
 3 files changed, 48 insertions(+), 2 deletions(-)

diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index b88b29ec23..a7e535c27f 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -538,7 +538,28 @@ find_single_rel_for_clauses(PlannerInfo *root, List *clauses)
 		 * However, currently the extended-stats machinery won't do anything
 		 * with non-RestrictInfo clauses anyway, so there's no point in
 		 * spending extra cycles; just fail if that's what we have.
+		 *
+		 * An exception to that rule is if we have a bare BoolExpr AND clause.
+		 * We treat this as a special case because the restrictinfo machinery
+		 * doesn't build RestrictInfos on top of AND clauses.
 		 */
+		if (is_andclause(rinfo))
+		{
+			RelOptInfo *rel;
+
+			rel = find_single_rel_for_clauses(root,
+											  ((BoolExpr *) rinfo)->args);
+
+			if (rel == NULL)
+				return NULL;
+			if (lastrelid == 0)
+				lastrelid = rel->relid;
+			else if (rel->relid != lastrelid)
+				return NULL;
+
+			continue;
+		}
+
 		if (!IsA(rinfo, RestrictInfo))
 			return NULL;
 
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index b6bd12c229..9745a2f248 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -1174,6 +1174,31 @@ statext_is_compatible_clause(PlannerInfo *root, Node *clause, Index relid,
 	RestrictInfo *rinfo = (RestrictInfo *) clause;
 	Oid			userid;
 
+	/*
+	 * Special-case handling for bare BoolExpr AND clauses, because the
+	 * restrictinfo machinery doesn't build RestrictInfos on top of AND
+	 * clauses.
+	 */
+	if (is_andclause(clause))
+	{
+		BoolExpr   *expr = (BoolExpr *) clause;
+		ListCell   *lc;
+
+		/*
+		 * Check that each sub-clause is compatible.  We expect these to be
+		 * RestrictInfos.
+		 */
+		foreach(lc, expr->args)
+		{
+			if (!statext_is_compatible_clause(root, (Node *) lfirst(lc),
+											  relid, attnums))
+				return false;
+		}
+
+		return true;
+	}
+
+	/* Otherwise it must be a RestrictInfo. */
 	if (!IsA(rinfo, RestrictInfo))
 		return false;
 
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 6e1c4f3edd..7bfeaf85f0 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -1594,13 +1594,13 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0 AND c = 0) OR (a = 1 AND b = 1 AND c = 1) OR (a = 2 AND b = 2 AND c = 2)');
  estimated | actual 
 -----------+--------
-       300 |    306
+       306 |    306
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0) OR (a = 0 AND c = 0) OR (b = 0 AND c = 0)');
  estimated | actual 
 -----------+--------
-       306 |    102
+       108 |    102
 (1 row)
 
 DROP TABLE mcv_lists_partial;
-- 
2.26.2

