From ac411f30a1a30f58037234fffc7e88e6f2eb636c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= <yizhi.fzh@alibaba-inc.com>
Date: Mon, 9 Aug 2021 19:10:35 +0800
Subject: [PATCH v3 4/6] Support UniqueKey on BaseRel.

---
 src/backend/optimizer/path/Makefile     |   3 +-
 src/backend/optimizer/path/allpaths.c   |   7 +-
 src/backend/optimizer/path/uniquekey.c  | 400 ++++++++++++++++++++++++
 src/backend/optimizer/plan/planner.c    |  10 +
 src/backend/optimizer/util/relnode.c    |   2 +
 src/include/nodes/nodes.h               |   3 +-
 src/include/nodes/pathnodes.h           |  20 ++
 src/include/optimizer/paths.h           |   4 +
 src/test/regress/expected/join.out      |  11 +-
 src/test/regress/expected/uniquekey.out |  82 +++++
 src/test/regress/parallel_schedule      |   2 +-
 src/test/regress/sql/uniquekey.sql      |  29 ++
 12 files changed, 562 insertions(+), 11 deletions(-)
 create mode 100644 src/backend/optimizer/path/uniquekey.c
 create mode 100644 src/test/regress/expected/uniquekey.out
 create mode 100644 src/test/regress/sql/uniquekey.sql

diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..63cc1505d9 100644
--- a/src/backend/optimizer/path/Makefile
+++ b/src/backend/optimizer/path/Makefile
@@ -21,6 +21,7 @@ OBJS = \
 	joinpath.o \
 	joinrels.o \
 	pathkeys.o \
-	tidpath.o
+	tidpath.o \
+	uniquekey.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index a63a3dfe00..343253d694 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -396,6 +396,9 @@ static void
 set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 			 Index rti, RangeTblEntry *rte)
 {
+	/* Set the notnull before the UniqueKey population. */
+	set_baserel_notnull_attrs(rel);
+
 	if (rel->reloptkind == RELOPT_BASEREL &&
 		relation_excluded_by_constraints(root, rel, rte))
 	{
@@ -491,7 +494,7 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 		}
 	}
 
-	set_baserel_notnull_attrs(rel);
+
 
 	/*
 	 * We insist that all non-dummy rels have a nonzero rowcount estimate.
@@ -616,6 +619,8 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	 */
 	check_index_predicates(root, rel);
 
+	populate_baserel_uniquekeys(root, rel);
+
 	/* Mark rel with estimated output rows, width, etc */
 	set_baserel_size_estimates(root, rel);
 }
diff --git a/src/backend/optimizer/path/uniquekey.c b/src/backend/optimizer/path/uniquekey.c
new file mode 100644
index 0000000000..c93075656d
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekey.c
@@ -0,0 +1,400 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekey.c
+ *	  Utilities for maintaining uniquekey.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/optimizer/path/uniquekey.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/sysattr.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/pathnodes.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/paths.h"
+
+
+/* Functions to populate UniqueKey */
+static bool add_uniquekey_for_uniqueindex(PlannerInfo *root,
+										  IndexOptInfo *unique_index,
+										  List *mergeable_const_peer,
+										  List *expr_opfamilies);
+
+/* UniqueKey is subset of .. */
+static bool uniquekey_contains_in(PlannerInfo *root, UniqueKey *ukey,
+								  List *ecs, Relids relids);
+
+/* Avoid useless UniqueKey. */
+static bool unique_ecs_useful_for_distinct(PlannerInfo *root, List *ecs);
+static bool unique_ecs_useful_for_merging(PlannerInfo *root, RelOptInfo *rel,
+										  List *unique_ecs);
+/* Helper functions to create UniqueKey. */
+static UniqueKey *make_uniquekey(Bitmapset *unique_expr_indexes,
+								 bool multi_null,
+								 bool useful_for_distinct);
+static void mark_rel_singlerow(PlannerInfo *root, RelOptInfo *rel);
+
+/* Debug only */
+static void print_uniquekey(PlannerInfo *root, RelOptInfo *rel);
+
+/*
+ * populate_baserel_uniquekeys
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root, RelOptInfo *rel)
+{
+	ListCell	*lc;
+	List	*mergeable_const_peer = NIL, *expr_opfamilies = NIL;
+
+	/*
+	 * ColX = {Const} AND ColY = {Const2} AND ColZ > {Const3},
+	 * gather ColX and ColY into mergeable_const_peer.
+	 */
+	foreach(lc, rel->baserestrictinfo)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+		if (rinfo->mergeopfamilies == NIL)
+			continue;
+
+		if (bms_is_empty(rinfo->left_relids))
+			mergeable_const_peer = lappend(mergeable_const_peer, get_rightop(rinfo->clause));
+		else if (bms_is_empty(rinfo->right_relids))
+			mergeable_const_peer = lappend(mergeable_const_peer, get_leftop(rinfo->clause));
+		else
+			continue;
+		expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+	}
+
+	foreach(lc, rel->indexlist)
+	{
+		IndexOptInfo *index = (IndexOptInfo *)lfirst(lc);
+		if (!index->unique || !index->immediate ||
+			(index->indpred != NIL && !index->predOK))
+			continue;
+
+		if (add_uniquekey_for_uniqueindex(root, index,
+										  mergeable_const_peer,
+										  expr_opfamilies))
+			/* Find a singlerow case, no need to go through any more. */
+			return;
+	}
+
+	print_uniquekey(root, rel);
+}
+
+/*
+ * relation_is_distinct_for
+ *		Check if the relation is distinct for.
+ */
+bool
+relation_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *distinct_pathkey)
+{
+	ListCell	*lc;
+	List	*lecs = NIL;
+	Relids	relids = NULL;
+	foreach(lc, distinct_pathkey)
+	{
+		PathKey *pathkey = lfirst(lc);
+		lecs = lappend(lecs, pathkey->pk_eclass);
+		/*
+		 * Note that ec_relids doesn't include child member, but
+		 * distinct would not operate on childrel as well.
+		 */
+		relids = bms_union(relids, pathkey->pk_eclass->ec_relids);
+	}
+
+	foreach(lc, rel->uniquekeys)
+	{
+		UniqueKey *ukey = lfirst(lc);
+		if (ukey->multi_nulls)
+			continue;
+
+		if (uniquekey_contains_in(root, ukey, lecs, relids))
+			return true;
+	}
+	return false;
+}
+
+/*
+ * add_uniquekey_for_uniqueindex
+ *	 populate a UniqueKey if necessary, return true iff the UniqueKey is an
+ * SingleRow.
+ */
+static bool
+add_uniquekey_for_uniqueindex(PlannerInfo *root, IndexOptInfo *unique_index,
+							  List *mergeable_const_peer, List *expr_opfamilies)
+{
+	List	*unique_exprs = NIL, *unique_ecs = NIL;
+	ListCell	*indexpr_item;
+	int	c = 0;
+	RelOptInfo *rel = unique_index->rel;
+	bool	multinull = false;
+	bool	used_for_distinct = false;
+	Bitmapset *unique_exprs_index;
+
+	indexpr_item = list_head(unique_index->indexprs);
+	/* Gather all the non-const exprs */
+	for (c = 0; c < unique_index->nkeycolumns; c++)
+	{
+		int attr = unique_index->indexkeys[c];
+		Expr *expr;
+		bool	matched_const = false;
+		ListCell	*lc1, *lc2;
+		if (attr > 0)
+		{
+			Var *var;
+			expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+			var = castNode(Var, expr);
+			Assert(IsA(expr, Var));
+			if (!bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
+							  rel->notnull_attrs[0]))
+				multinull = true;
+		}
+		else if (attr == 0)
+		{
+			/* Expression index */
+			expr = lfirst(indexpr_item);
+			indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+			/* We can't grantee an FuncExpr will not return NULLs */
+			multinull = true;
+		}
+		else /* attr < 0 */
+		{
+			/* Index on OID is possible, not handle it for now. */
+			return false;
+		}
+
+		/*
+		 * Check index_col = Const case with regarding to opfamily checking
+		 * If so, we can remove the index_col from the final UniqueKey->exprs.
+		 */
+		forboth(lc1, mergeable_const_peer, lc2, expr_opfamilies)
+		{
+			if (list_member_oid((List *) lfirst(lc2), unique_index->opfamily[c]) &&
+				match_index_to_operand((Node *) lfirst(lc1), c, unique_index))
+			{
+				matched_const = true;
+				break;
+			}
+		}
+
+		if (matched_const)
+			continue;
+
+		unique_exprs = lappend(unique_exprs, expr);
+	}
+
+	if (unique_exprs == NIL)
+	{
+		/*
+		 * SingleRow case. Checking if it is useful is ineffective
+		 * so just keep it.
+		 */
+		mark_rel_singlerow(root, rel);
+		return true;
+	}
+
+	unique_ecs = build_equivalanceclass_list_for_exprs(root, unique_exprs, rel);
+
+	if (unique_ecs == NIL)
+	{
+		/* It is neither used in distinct_pathkey nor mergeable clause */
+		return false;
+	}
+
+	/*
+	 * Check if we need to setup the UniqueKey and set the used_for_distinct accordingly.
+	 */
+	if (unique_ecs_useful_for_distinct(root, unique_ecs))
+	{
+		used_for_distinct = true;
+	}
+	else if (!unique_ecs_useful_for_merging(root, rel, unique_ecs))
+		/*
+		 * Neither used in distinct pathkey nor used in mergeable clause.
+		 * this is possible even if unique_ecs != NIL.
+		 */
+		return false;
+	else
+	{
+		/*
+		 * unique_ecs_useful_for_merging(root, rel, unique_ecs) is true,
+		 * we did nothing in this case.
+		 */
+	}
+	unique_exprs_index = bms_make_singleton(list_length(root->unique_exprs));
+	root->unique_exprs = lappend(root->unique_exprs, unique_ecs);
+	rel->uniquekeys = lappend(rel->uniquekeys,
+							  make_uniquekey(unique_exprs_index,
+											 multinull,
+											 used_for_distinct));
+	return false;
+}
+/*
+ * uniquekey_contains_in
+ *	Return if UniqueKey contains in the list of EquivalenceClass
+ * or the UniqueKey's SingleRow contains in relids.
+ *
+ */
+static bool
+uniquekey_contains_in(PlannerInfo *root, UniqueKey *ukey, List *ecs, Relids relids)
+{
+	int i = -1;
+	while ((i = bms_next_member(ukey->unique_expr_indexes, i)) >= 0)
+	{
+		Node *exprs = list_nth(root->unique_exprs, i);
+		if (IsA(exprs, SingleRow))
+		{
+			SingleRow *singlerow = castNode(SingleRow, exprs);
+			if (!bms_is_member(singlerow->relid, relids))
+				/*
+				 * UniqueKey request a ANY expr on relid on the relid(which
+				 * indicates we don't have other EquivalenceClass for this
+				 * relation), but the relid doesn't contains in relids, which
+				 * indicate there is no such Expr in target, then we are sure
+				 * to return false.
+				 */
+				return false;
+			else
+			{
+				/*
+				 * We have SingleRow on relid, and the relid is in relids.
+				 * We don't need to check any more for this expr. This is
+				 * right for sure.
+				 */
+			}
+		}
+		else
+		{
+			Assert(IsA(exprs, List));
+			if (!list_is_subset_ptr((List *)exprs, ecs))
+				return false;
+		}
+	}
+	return true;
+}
+
+/*
+ * unique_ecs_useful_for_distinct
+ *	return true if all the EquivalenceClass in ecs exists in root->distinct_pathkey.
+ */
+static bool
+unique_ecs_useful_for_distinct(PlannerInfo *root, List *ecs)
+{
+	ListCell *lc;
+	foreach(lc, ecs)
+	{
+		EquivalenceClass *ec = lfirst_node(EquivalenceClass, lc);
+		ListCell *p;
+		bool found = false;
+		foreach(p,  root->distinct_pathkeys)
+		{
+			PathKey *pathkey = lfirst_node(PathKey, p);
+			/*
+			 * Both of them should point to an element in root->eq_classes.
+			 * so the address should be same. and equal function doesn't
+			 * support EquivalenceClass yet.
+			 */
+			if (ec == pathkey->pk_eclass)
+			{
+				found = true;
+				break;
+			}
+		}
+		if (!found)
+			return false;
+	}
+	return true;
+}
+
+/*
+ * unique_ecs_useful_for_merging
+ *	return true if all the unique_ecs exists in rel's join restrictInfo.
+ */
+static bool
+unique_ecs_useful_for_merging(PlannerInfo *root, RelOptInfo *rel, List *unique_ecs)
+{
+	ListCell	*lc;
+
+	foreach(lc, unique_ecs)
+	{
+		EquivalenceClass *ec = lfirst(lc);
+		if (!ec_useful_for_merging(root, rel, ec))
+			return false;
+	}
+
+	return true;
+}
+/*
+ *	make_uniquekey
+ */
+static UniqueKey *
+make_uniquekey(Bitmapset *unique_expr_indexes, bool multi_null, bool useful_for_distinct)
+{
+	UniqueKey *ukey = makeNode(UniqueKey);
+	ukey->unique_expr_indexes = unique_expr_indexes;
+	ukey->multi_nulls = multi_null;
+	ukey->use_for_distinct = useful_for_distinct;
+	return ukey;
+}
+
+/*
+ * mark_rel_singlerow
+ *	mark a relation as singlerow.
+ */
+static void
+mark_rel_singlerow(PlannerInfo *root, RelOptInfo *rel)
+{
+	int exprs_pos = list_length(root->unique_exprs);
+	Bitmapset *unique_exprs_index = bms_make_singleton(exprs_pos);
+	SingleRow *singlerow = makeNode(SingleRow);
+	singlerow->relid = rel->relid;
+	root->unique_exprs = lappend(root->unique_exprs, singlerow);
+	rel->uniquekeys = list_make1(make_uniquekey(unique_exprs_index,
+												false /* multi-null */,
+												true /* arbitrary decision */));
+}
+
+/*
+ * print_uniquekey
+ *	Used for easier reivew, should be removed before commit.
+ */
+static void
+print_uniquekey(PlannerInfo *root, RelOptInfo *rel)
+{
+	if (false)
+	{
+		ListCell	*lc;
+		elog(INFO, "Rel = %s", bmsToString(rel->relids));
+		foreach(lc, rel->uniquekeys)
+		{
+			UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+			int i = -1;
+			elog(INFO, "UNIQUEKEY{indexes=%s, multinull=%d}",
+				 bmsToString(ukey->unique_expr_indexes),
+				 ukey->multi_nulls
+				);
+
+			while ((i = bms_next_member(ukey->unique_expr_indexes, i)) >= 0)
+			{
+				Node *node = (Node *) list_nth(root->unique_exprs, i);
+				if (IsA(node, SingleRow))
+					elog(INFO,
+						 "Expr(%d) SingleRow{relid = %d}",
+						 i, castNode(SingleRow, node)->relid);
+				else
+					elog(INFO,
+						 "EC(%d), %s", i, nodeToString(node)
+						);
+			}
+		}
+	}
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 2cd691191c..ed2cff00fc 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -4238,6 +4238,16 @@ create_distinct_paths(PlannerInfo *root,
 	Path	   *path;
 	ListCell   *lc;
 
+	/*
+	 * distinct_pathkeys may be NIL if it distinctClause is sortable.
+	 * see standard_qp_callback. But for the efficiency of relation_is_distinct_for
+	 * we can't use distinctClause (rather than EC) here. Fortunately not sortable
+	 * clause is rare in real case.
+	 */
+	if (root->distinct_pathkeys &&
+		relation_is_distinct_for(root, input_rel, root->distinct_pathkeys))
+		return input_rel;
+
 	/* For now, do all work in the (DISTINCT, NULL) upperrel */
 	distinct_rel = fetch_upper_rel(root, UPPERREL_DISTINCT, NULL);
 
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index f017a7d52d..b75e1679e6 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -261,6 +261,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	rel->partexprs = NULL;
 	rel->nullable_partexprs = NULL;
 	rel->notnull_attrs = palloc0(sizeof(Bitmapset *) * 1);
+	rel->uniquekeys = NIL;
 
 	/*
 	 * Pass assorted information down the inheritance hierarchy.
@@ -752,6 +753,7 @@ build_join_rel(PlannerInfo *root,
 	joinrel->partexprs = NULL;
 	joinrel->nullable_partexprs = NULL;
 	joinrel->notnull_attrs = palloc0(sizeof(Bitmapset *) * (bms_max_member(joinrel->relids) + 1));
+	joinrel->uniquekeys = NIL;
 
 	/* Compute information relevant to the foreign relations. */
 	set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6a4d82f0a8..1bf220f373 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -280,7 +280,8 @@ typedef enum NodeTag
 	T_RollupData,
 	T_GroupingSetData,
 	T_StatisticExtInfo,
-
+	T_UniqueKey,
+	T_SingleRow,
 	/*
 	 * TAGS FOR MEMORY NODES (memnodes.h)
 	 */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 7bf1896e12..3581b03a75 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -246,6 +246,7 @@ struct PlannerInfo
 									 * subquery outputs */
 
 	List	   *eq_classes;		/* list of active EquivalenceClasses */
+	List	   *unique_exprs;		/* List of unique expr */
 
 	bool		ec_merging_done;	/* set true once ECs are canonical */
 
@@ -691,6 +692,7 @@ typedef struct RelOptInfo
 								  * the len would always 1. and for others the array
 								  * index is relid from relids.
 								  */
+	List		*uniquekeys; /* A list of UniqueKey. */
 
 	/* materialization information */
 	List	   *pathlist;		/* Path structures */
@@ -1070,6 +1072,24 @@ typedef struct PathKey
 	bool		pk_nulls_first; /* do NULLs come before normal values? */
 } PathKey;
 
+
+typedef struct UnqiueKey
+{
+	NodeTag	type;
+	Bitmapset	*unique_expr_indexes;
+	bool	multi_nulls;
+	bool	use_for_distinct;  /* true if it is used in distinct-pathkey, in this case
+								* we would never check if we should discard it during
+								* join search.
+								*/
+} UniqueKey;
+
+typedef struct SingleRow
+{
+	NodeTag	type;
+	Index		relid;
+} SingleRow;
+
 /*
  * VolatileFunctionStatus -- allows nodes to cache their
  * contain_volatile_functions properties. VOLATILITY_UNKNOWN means not yet
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index e813d82483..68b8b40ca9 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -263,4 +263,8 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
 extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 									List *live_childrels);
 
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+										RelOptInfo *baserel);
+extern bool relation_is_distinct_for(PlannerInfo *root, RelOptInfo *rel,
+									 List *distinct_pathkey);
 #endif							/* PATHS_H */
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index f3589d0dbb..db72374302 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -4612,18 +4612,15 @@ select d.* from d left join (select * from b group by b.id, b.c_id) s
 explain (costs off)
 select d.* from d left join (select distinct * from b) s
   on d.a = s.id;
-              QUERY PLAN              
---------------------------------------
+             QUERY PLAN             
+------------------------------------
  Merge Right Join
    Merge Cond: (b.id = d.a)
-   ->  Unique
-         ->  Sort
-               Sort Key: b.id, b.c_id
-               ->  Seq Scan on b
+   ->  Index Scan using b_pkey on b
    ->  Sort
          Sort Key: d.a
          ->  Seq Scan on d
-(9 rows)
+(6 rows)
 
 -- check join removal works when uniqueness of the join condition is enforced
 -- by a UNION
diff --git a/src/test/regress/expected/uniquekey.out b/src/test/regress/expected/uniquekey.out
new file mode 100644
index 0000000000..d9a8634e80
--- /dev/null
+++ b/src/test/regress/expected/uniquekey.out
@@ -0,0 +1,82 @@
+CREATE TABLE uqk1(a int, pk int primary key, c int,  d int);
+CREATE TABLE uqk2(a int, pk int primary key, c int,  d int);
+INSERT INTO uqk1 VALUES(1, 1, 1, 1), (2, 2, 2, 2), (3, 3, 3, 3), (4, 4, null, 4), (5, 5, null, 4);
+INSERT INTO uqk2 VALUES(1, 1, 1, 1), (4, 4, 4, 4), (5, 5, 5, 5);
+ANALYZE uqk1;
+ANALYZE uqk2;
+-- Test single table primary key.
+EXPLAIN (COSTS OFF) SELECT DISTINCT * FROM uqk1;
+    QUERY PLAN    
+------------------
+ Seq Scan on uqk1
+(1 row)
+
+-- Test EC case.
+EXPLAIN (COSTS OFF) SELECT DISTINCT d FROM uqk1 WHERE d = pk;
+     QUERY PLAN     
+--------------------
+ Seq Scan on uqk1
+   Filter: (d = pk)
+(2 rows)
+
+-- Test UniqueKey indexes.
+CREATE UNIQUE INDEX uqk1_ukcd ON uqk1(c, d);
+-- Test not null quals and not null per catalog.
+EXPLAIN (COSTS OFF) SELECT DISTINCT c, d FROM uqk1;
+       QUERY PLAN       
+------------------------
+ HashAggregate
+   Group Key: c, d
+   ->  Seq Scan on uqk1
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT DISTINCT c, d FROM uqk1 WHERE c is NOT NULL;
+              QUERY PLAN               
+---------------------------------------
+ Unique
+   ->  Sort
+         Sort Key: c, d
+         ->  Seq Scan on uqk1
+               Filter: (c IS NOT NULL)
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT DISTINCT d FROM uqk1 WHERE c = 1;
+          QUERY PLAN           
+-------------------------------
+ Unique
+   ->  Sort
+         Sort Key: d
+         ->  Seq Scan on uqk1
+               Filter: (c = 1)
+(5 rows)
+
+ALTER TABLE uqk1 ALTER COLUMN d SET NOT NULL;
+EXPLAIN (COSTS OFF) SELECT DISTINCT c, d FROM uqk1 WHERE c is NOT NULL;
+        QUERY PLAN         
+---------------------------
+ Seq Scan on uqk1
+   Filter: (c IS NOT NULL)
+(2 rows)
+
+-- Test UniqueKey column reduction.
+EXPLAIN (COSTS OFF) SELECT DISTINCT d FROM uqk1 WHERE c = 1;
+    QUERY PLAN     
+-------------------
+ Seq Scan on uqk1
+   Filter: (c = 1)
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT DISTINCT a FROM uqk1 WHERE c = 1 and d = 1;
+           QUERY PLAN            
+---------------------------------
+ Seq Scan on uqk1
+   Filter: ((c = 1) AND (d = 1))
+(2 rows)
+
+-- Test Distinct ON
+EXPLAIN (COSTS OFF) SELECT DISTINCT ON(pk) d FROM uqk1;
+    QUERY PLAN    
+------------------
+ Seq Scan on uqk1
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0..41454448f1 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -72,7 +72,7 @@ test: sanity_check
 # ----------
 ignore: random
 test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update delete namespace prepared_xacts
-
+test: uniquekey
 # ----------
 # Another group of parallel tests
 # ----------
diff --git a/src/test/regress/sql/uniquekey.sql b/src/test/regress/sql/uniquekey.sql
new file mode 100644
index 0000000000..a1b538d1c1
--- /dev/null
+++ b/src/test/regress/sql/uniquekey.sql
@@ -0,0 +1,29 @@
+CREATE TABLE uqk1(a int, pk int primary key, c int,  d int);
+CREATE TABLE uqk2(a int, pk int primary key, c int,  d int);
+INSERT INTO uqk1 VALUES(1, 1, 1, 1), (2, 2, 2, 2), (3, 3, 3, 3), (4, 4, null, 4), (5, 5, null, 4);
+INSERT INTO uqk2 VALUES(1, 1, 1, 1), (4, 4, 4, 4), (5, 5, 5, 5);
+ANALYZE uqk1;
+ANALYZE uqk2;
+
+-- Test single table primary key.
+EXPLAIN (COSTS OFF) SELECT DISTINCT * FROM uqk1;
+
+-- Test EC case.
+EXPLAIN (COSTS OFF) SELECT DISTINCT d FROM uqk1 WHERE d = pk;
+
+-- Test UniqueKey indexes.
+CREATE UNIQUE INDEX uqk1_ukcd ON uqk1(c, d);
+
+-- Test not null quals and not null per catalog.
+EXPLAIN (COSTS OFF) SELECT DISTINCT c, d FROM uqk1;
+EXPLAIN (COSTS OFF) SELECT DISTINCT c, d FROM uqk1 WHERE c is NOT NULL;
+EXPLAIN (COSTS OFF) SELECT DISTINCT d FROM uqk1 WHERE c = 1;
+ALTER TABLE uqk1 ALTER COLUMN d SET NOT NULL;
+EXPLAIN (COSTS OFF) SELECT DISTINCT c, d FROM uqk1 WHERE c is NOT NULL;
+
+-- Test UniqueKey column reduction.
+EXPLAIN (COSTS OFF) SELECT DISTINCT d FROM uqk1 WHERE c = 1;
+EXPLAIN (COSTS OFF) SELECT DISTINCT a FROM uqk1 WHERE c = 1 and d = 1;
+
+-- Test Distinct ON
+EXPLAIN (COSTS OFF) SELECT DISTINCT ON(pk) d FROM uqk1;
-- 
2.21.0

