public inbox for [email protected]
help / color / mirror / Atom feed[PATCH 03/10] Introduce UniqueKey attributes on RelOptInfo struct.
49+ messages / 3 participants
[nested] [flat]
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 03/10] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index fc1a3a68a2..66bf6f19f7 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1299,6 +1305,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2306,6 +2314,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.30.2
--------------F495F6B18672582269F00FF9
Content-Type: text/x-patch; charset=UTF-8;
name="0004-review-20210317.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="0004-review-20210317.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 03/10] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index fc1a3a68a2..66bf6f19f7 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1299,6 +1305,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2306,6 +2314,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.30.2
--------------F495F6B18672582269F00FF9
Content-Type: text/x-patch; charset=UTF-8;
name="0004-review-20210317.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="0004-review-20210317.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v37 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2b4d7654cc..34392f5553 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2271,6 +2271,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5149,6 +5159,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index efa44342c4..20daf4a9fd 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 08a049232e..53cf4fcfa1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2426,6 +2426,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4125,6 +4133,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ab7b535caa..7b9e8c3292 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2654,6 +2662,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 754f6d64f6..66d246fa1a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1314,6 +1320,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2387,6 +2395,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 4a35903b29..f41be18e82 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..c7ad76d28f
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ 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)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 986d7a52e3..f1bbb8c427 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2385,6 +2385,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3895,6 +3897,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4929,7 +4933,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5190,6 +5194,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6067,6 +6073,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 745f443e5c..ce290cb97b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ddd8c011b..2bfbd353c7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -263,6 +263,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 925f2eac3f..5737cd76ce 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -729,6 +729,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1046,6 +1047,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2472,7 +2495,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2502,8 +2525,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index ec231010ce..a1e279815c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -543,6 +543,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--snesplnlompos6d4
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v37-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1131 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 16 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 2 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1502 insertions(+), 23 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..1f50400fd2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2273,6 +2273,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5152,6 +5162,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 80fa8c84e4..a7a99b70f2 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -687,6 +687,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..646cf7c9a1 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..c3a9632992 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2428,6 +2428,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4127,6 +4135,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..3a18571d0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -452,6 +452,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2656,6 +2664,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 484dab0a1a..2ad9d06d7a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1310,6 +1316,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2383,6 +2391,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index db54a6ba2e..ef0fd2fb0b 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 2d343cd293..b9163ee8ff 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index ce9bf87e9b..7e596d4194 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..b33bcd2f32
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1131 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(RelOptInfo *joinrel, RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..0551ae0512 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2389,6 +2389,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4615,7 +4619,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4911,7 +4915,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5172,6 +5176,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6049,6 +6055,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 951aed80e7..e94e92937c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063cf3..44c37ecffc 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd35a5..d66b40ec50 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,13 +748,14 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo((Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo((Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..c83f17acb7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -105,4 +106,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..41110ed888 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -264,6 +264,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9e3ebd488a..02e4458bef 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -730,6 +730,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1047,6 +1048,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2473,7 +2496,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2503,8 +2526,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 14ea2766ad..621f54a9f8 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -528,6 +528,8 @@ extern bool list_member_ptr(const List *list, const void *datum);
extern bool list_member_int(const List *list, int datum);
extern bool list_member_oid(const List *list, Oid datum);
+extern bool list_is_subset(const List *members, const List *target);
+
extern List *list_delete(List *list, void *datum);
extern List *list_delete_ptr(List *list, void *datum);
extern List *list_delete_int(List *list, int datum);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a60dd..e87c92a054 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e4171056e..9445141263 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* We don't want to include nodes/pathnodes.h here, because non-planner
@@ -156,6 +157,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 10b6e81079..9217a8d6c6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -240,5 +240,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.21.0
--op7mf2i72xk5yu7c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v36-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct.
@ 2020-05-11 07:50 一挃 <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: 一挃 @ 2020-05-11 07:50 UTC (permalink / raw)
UniqueKey is a set of exprs on RelOptInfo which represents the exprs
will be unique on the given RelOptInfo. You can see README.uniquekey
for more information.
---
src/backend/nodes/copyfuncs.c | 13 +
src/backend/nodes/list.c | 31 +
src/backend/nodes/makefuncs.c | 13 +
src/backend/nodes/outfuncs.c | 11 +
src/backend/nodes/readfuncs.c | 10 +
src/backend/optimizer/path/Makefile | 3 +-
src/backend/optimizer/path/README.uniquekey | 131 +++
src/backend/optimizer/path/allpaths.c | 10 +
src/backend/optimizer/path/joinpath.c | 9 +-
src/backend/optimizer/path/joinrels.c | 2 +
src/backend/optimizer/path/pathkeys.c | 3 +-
src/backend/optimizer/path/uniquekeys.c | 1135 +++++++++++++++++++
src/backend/optimizer/plan/planner.c | 13 +-
src/backend/optimizer/prep/prepunion.c | 2 +
src/backend/optimizer/util/appendinfo.c | 44 +
src/backend/optimizer/util/inherit.c | 18 +-
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/pathnodes.h | 29 +-
src/include/nodes/pg_list.h | 10 +
src/include/optimizer/appendinfo.h | 3 +
src/include/optimizer/optimizer.h | 2 +
src/include/optimizer/paths.h | 43 +
23 files changed, 1515 insertions(+), 24 deletions(-)
create mode 100644 src/backend/optimizer/path/README.uniquekey
create mode 100644 src/backend/optimizer/path/uniquekeys.c
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da91cbd2b1..75c1c5e824 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,16 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+static UniqueKey *
+_copyUniqueKey(const UniqueKey *from)
+{
+ UniqueKey *newnode = makeNode(UniqueKey);
+
+ COPY_NODE_FIELD(exprs);
+ COPY_SCALAR_FIELD(multi_nullvals);
+
+ return newnode;
+}
/*
* _copyRestrictInfo
*/
@@ -5220,6 +5230,9 @@ copyObjectImpl(const void *from)
case T_PathKey:
retval = _copyPathKey(from);
break;
+ case T_UniqueKey:
+ retval = _copyUniqueKey(from);
+ break;
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index dbf6b30233..ca099495a1 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -702,6 +702,37 @@ list_member_oid(const List *list, Oid datum)
return false;
}
+/*
+ * return true iff every entry in "members" list is also present
+ * in the "target" list.
+ */
+bool
+list_is_subset(const List *members, const List *target)
+{
+ const ListCell *lc1, *lc2;
+
+ Assert(IsPointerList(members));
+ Assert(IsPointerList(target));
+ check_list_invariants(members);
+ check_list_invariants(target);
+
+ foreach(lc1, members)
+ {
+ bool found = false;
+ foreach(lc2, target)
+ {
+ if (equal(lfirst(lc1), lfirst(lc2)))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
/*
* Delete the n'th cell (counting from 0) in list.
*
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..40415d0f5b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,16 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
v->va_cols = va_cols;
return v;
}
+
+
+/*
+ * makeUniqueKey
+ */
+UniqueKey*
+makeUniqueKey(List *exprs, bool multi_nullvals)
+{
+ UniqueKey * ukey = makeNode(UniqueKey);
+ ukey->exprs = exprs;
+ ukey->multi_nullvals = multi_nullvals;
+ return ukey;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6493a03ff8..44154cde6a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2456,6 +2456,14 @@ _outPathKey(StringInfo str, const PathKey *node)
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outUniqueKey(StringInfo str, const UniqueKey *node)
+{
+ WRITE_NODE_TYPE("UNIQUEKEY");
+ WRITE_NODE_FIELD(exprs);
+ WRITE_BOOL_FIELD(multi_nullvals);
+}
+
static void
_outPathTarget(StringInfo str, const PathTarget *node)
{
@@ -4198,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_UniqueKey:
+ _outUniqueKey(str, obj);
+ break;
case T_PathTarget:
_outPathTarget(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c5e136e9c3..b3e212bf1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -492,6 +492,14 @@ _readSetOperationStmt(void)
READ_DONE();
}
+static UniqueKey *
+_readUniqueKey(void)
+{
+ READ_LOCALS(UniqueKey);
+ READ_NODE_FIELD(exprs);
+ READ_BOOL_FIELD(multi_nullvals);
+ READ_DONE();
+}
/*
* Stuff from primnodes.h.
@@ -2717,6 +2725,8 @@ parseNodeString(void)
return_value = _readCommonTableExpr();
else if (MATCH("SETOPERATIONSTMT", 16))
return_value = _readSetOperationStmt();
+ else if (MATCH("UNIQUEKEY", 9))
+ return_value = _readUniqueKey();
else if (MATCH("ALIAS", 5))
return_value = _readAlias();
else if (MATCH("RANGEVAR", 8))
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..7b9820c25f 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 \
+ uniquekeys.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
new file mode 100644
index 0000000000..5eac761995
--- /dev/null
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -0,0 +1,131 @@
+1. What is UniqueKey?
+We can think UniqueKey is a set of exprs for a RelOptInfo, which we are insure
+that doesn't yields same result among all the rows. The simplest UniqueKey
+format is primary key.
+
+However we define the UnqiueKey as below.
+
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
+exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+is a special case of UniqueKey, which means there is only one row in that
+relation.it has a stronger semantic than others. like SELECT uk FROM t; uk is
+normal unique key and may have different values. SELECT colx FROM t WHERE uk =
+const. colx is unique AND we have only 1 value. This field can used for
+innerrel_is_unique. this logic is handled specially in add_uniquekey_for_onerow
+function.
+
+multi_nullvals: true means multi null values may exist in these exprs, so the
+uniqueness is not guaranteed in this case. This field is necessary for
+remove_useless_join & reduce_unique_semijoins where we don't mind these
+duplicated NULL values. It is set to true for 2 cases. One is a unique key
+from a unique index but the related column is nullable. The other one is for
+outer join. see populate_joinrel_uniquekeys for detail.
+
+
+The UniqueKey can be used at the following cases at least:
+1. remove_useless_joins.
+2. reduce_semianti_joins
+3. remove distinct node if distinct clause is unique.
+4. remove aggnode if group by clause is unique.
+5. Index Skip Scan (WIP)
+6. Aggregation Push Down without 2 phase aggregation if the join can't
+ duplicated the aggregated rows. (work in progress feature)
+
+2. How is it maintained?
+
+We have a set of populate_xxx_unqiuekeys functions to maintain the uniquekey on
+various cases. xxx includes baserel, joinrel, partitionedrel, distinctrel,
+groupedrel, unionrel. and we also need to convert the uniquekey from subquery
+to outer relation, which is what convert_subquery_uniquekeys does.
+
+1. The first part is about baserel. We handled 3 cases. suppose we have Unique
+Index on (a, b).
+
+1. SELECT a, b FROM t. UniqueKey (a, b)
+2. SELECT a FROM t WHERE b = 1; UniqueKey (a)
+3. SELECT .. FROM t WHERE a = 1 AND b = 1; UniqueKey (NIL). onerow case, every
+ column is Unique.
+
+2. The next part is joinrel, this part is most error-prone, we simplified the rules
+like below:
+1. If the relation's UniqueKey can't be duplicated after join, then is will be
+ still valid for the join rel. The function we used here is
+ innerrel_keeps_unique. The basic idea is innerrel.any_col = outer.uk.
+
+2. If the UnqiueKey can't keep valid via the rule 1, the combination of the
+ UniqueKey from both sides are valid for sure. We can prove this as: if the
+ unique exprs from rel1 is duplicated by rel2, the duplicated rows must
+ contains different unique exprs from rel2.
+
+More considerations about onerow:
+1. If relation with one row and it can't be duplicated, it is still possible
+ contains mulit_nullvas after outer join.
+2. If the either UniqueKey can be duplicated after join, the can get one row
+ only when both side is one row AND there is no outer join.
+3. Whenever the onerow UniqueKey is not a valid any more, we need to convert one
+ row UniqueKey to normal unique key since we don't store exprs for one-row
+ relation. get_exprs_from_uniquekeys will be used here.
+
+
+More considerations about multi_nullvals after join:
+1. If the original UnqiueKey has multi_nullvals, the final UniqueKey will have
+ mulit_nullvals in any case.
+2. If a unique key doesn't allow mulit_nullvals, after some outer join, it
+ allows some outer join.
+
+
+3. When we comes to subquery, we need to convert_subquery_unqiuekeys just like
+convert_subquery_pathkeys. Only the UniqueKey insides subquery is referenced as
+a Var in outer relation will be reused. The relationship between the outerrel.Var
+and subquery.exprs is built with outerel->subroot->processed_tlist.
+
+
+4. As for the SRF functions, it will break the uniqueness of uniquekey, However it
+is handled in adjust_paths_for_srfs, which happens after the query_planner. so
+we will maintain the UniqueKey until there and reset it to NIL at that
+places. This can't help on distinct/group by elimination cases but probably help
+in some other cases, like reduce_unqiue_semijoins/remove_useless_joins and it is
+semantic correctly.
+
+
+5. As for inherit table, we first main the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain 2 different kinds of
+UnqiueKey. 1). UniqueKey on the parent relation 2). UniqueKey on child
+relation for partition wise query.
+
+Example:
+CREATE TABLE p (a int not null, b int not null) partition by list (a);
+CREATE TABLE p0 partition of p for values in (1);
+CREATE TABLE p1 partition of p for values in (2);
+
+create unique index p0_b on p0(b);
+create unique index p1_b on p1(b);
+
+Now b is only unique on partition level, so the distinct can't be removed on
+the following cases. SELECT DISTINCT b FROM p;
+
+Another example is SELECT DISTINCT a, b FROM p WHERE a = 1; Since only one
+partition is chosen, the UniqueKey on child relation is same as the UniqueKey on
+parent relation.
+
+Another usage of UniqueKey on partition level is it be helpful for
+partition-wise join.
+
+As for the UniqueKey on parent table level, it comes with 2 different ways,
+1). the UniqueKey is also derived in UniqueKey index, but the index must be same
+in all the related children relations and the unique index must contains
+Partition Key in it. Example:
+
+CREATE UNIQUE INDEX p_ab ON p(a, b); -- where a is the partition key.
+
+-- Query
+SELECT a, b FROM p; the (a, b) is a UniqueKey of p.
+
+2). If the parent relation has only one childrel, the UniqueKey on childrel is
+ the UniqueKey on parent as well.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37b4223adb..d52ad59f89 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -579,6 +579,12 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+ /*
+ * Now that we've marked which partial indexes are suitable, we can now
+ * build the relation's unique keys.
+ */
+ populate_baserel_uniquekeys(root, rel, rel->indexlist);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
@@ -1297,6 +1303,8 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+ if (IS_PARTITIONED_REL(rel))
+ populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2304,6 +2312,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ convert_subquery_uniquekeys(root, rel, sub_final_rel);
+
/* If outer rel allows parallelism, do same for partial paths. */
if (rel->consider_parallel && bms_is_empty(required_outer))
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 57ce97fd53..697ac047fb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -71,13 +71,6 @@ static void consider_parallel_mergejoin(PlannerInfo *root,
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
-static List *select_mergejoin_clauses(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outerrel,
- RelOptInfo *innerrel,
- List *restrictlist,
- JoinType jointype,
- bool *mergejoin_allowed);
static void generate_mergejoin_paths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *innerrel,
@@ -1927,7 +1920,7 @@ hash_inner_and_outer(PlannerInfo *root,
* if it is mergejoinable and involves vars from the two sub-relations
* currently of interest.
*/
-static List *
+List *
select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 0dbe2ac726..7271f044ec 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -924,6 +924,8 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+
+ populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index bd9a176d7d..139278829b 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -33,7 +33,6 @@ static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
RelOptInfo *partrel,
int partkeycol);
-static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey);
@@ -1035,7 +1034,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
* We need this to ensure that we don't return pathkeys describing values
* that are unavailable above the level of the subquery scan.
*/
-static Var *
+Var *
find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle)
{
ListCell *lc;
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
new file mode 100644
index 0000000000..77ed2b2eff
--- /dev/null
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -0,0 +1,1135 @@
+/*-------------------------------------------------------------------------
+ *
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/optimizer/path/uniquekeys.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/appendinfo.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/tlist.h"
+#include "rewrite/rewriteManip.h"
+
+
+/*
+ * This struct is used to help populate_joinrel_uniquekeys.
+ *
+ * added_to_joinrel is true if a uniquekey (from outerrel or innerrel)
+ * has been added to joinrel.
+ * useful is true if the exprs of the uniquekey still appears in joinrel.
+ */
+typedef struct UniqueKeyContextData
+{
+ UniqueKey *uniquekey;
+ bool added_to_joinrel;
+ bool useful;
+} *UniqueKeyContext;
+
+static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static bool innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ bool reverse);
+
+static List *get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals);
+static List *get_exprs_from_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ UniqueKey *ukey);
+static void add_uniquekey_for_onerow(RelOptInfo *rel);
+static bool add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype);
+
+/* Used for unique indexes checking for partitioned table */
+static bool index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind);
+static IndexOptInfo *simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from);
+static bool simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2);
+static void adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_index);
+
+/* Helper function for grouped relation and distinct relation. */
+static void add_uniquekey_from_sortgroups(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *sortgroups);
+
+/*
+ * populate_baserel_uniquekeys
+ * Populate 'baserel' uniquekeys list by looking at the rel's unique index
+ * and baserestrictinfo
+ */
+void
+populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List *indexlist)
+{
+ ListCell *lc;
+ List *matched_uniq_indexes = NIL;
+
+ /* Attrs appears in rel->reltarget->exprs. */
+ Bitmapset *used_attrs = NULL;
+
+ List *const_exprs = NIL;
+ List *expr_opfamilies = NIL;
+
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ foreach(lc, indexlist)
+ {
+ IndexOptInfo *ind = (IndexOptInfo *) lfirst(lc);
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+ matched_uniq_indexes = lappend(matched_uniq_indexes, ind);
+ }
+
+ if (matched_uniq_indexes == NIL)
+ return;
+
+ /* Check which attrs is used in baserel->reltarget */
+ pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+
+ /* Check which attrno is used at a mergeable const filter */
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (rinfo->mergeopfamilies == NIL)
+ continue;
+
+ if (bms_is_empty(rinfo->left_relids))
+ {
+ const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
+ }
+ else if (bms_is_empty(rinfo->right_relids))
+ {
+ const_exprs = lappend(const_exprs, get_leftop(rinfo->clause));
+ }
+ else
+ continue;
+
+ expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
+ }
+
+ foreach(lc, matched_uniq_indexes)
+ {
+ bool multi_nullvals, useful;
+ List *exprs = get_exprs_from_uniqueindex(lfirst_node(IndexOptInfo, lc),
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+ if (useful)
+ {
+ if (exprs == NIL)
+ {
+ /* All the columns in Unique Index matched with a restrictinfo */
+ add_uniquekey_for_onerow(baserel);
+ return;
+ }
+ baserel->uniquekeys = lappend(baserel->uniquekeys,
+ makeUniqueKey(exprs, multi_nullvals));
+ }
+ }
+}
+
+
+/*
+ * populate_partitionedrel_uniquekeys
+ * The UniqueKey on partitionrel comes from 2 cases:
+ * 1). Only one partition is involved in this query, the unique key can be
+ * copied to parent rel from childrel.
+ * 2). There are some unique index which includes partition key and exists
+ * in all the related partitions.
+ * We never mind rule 2 if we hit rule 1.
+ */
+
+void
+populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels)
+{
+ ListCell *lc;
+ List *global_uniq_indexlist = NIL;
+ RelOptInfo *childrel;
+ bool is_first = true;
+
+ Assert(IS_PARTITIONED_REL(rel));
+
+ if (childrels == NIL)
+ return;
+
+ /*
+ * If there is only one partition used in this query, the UniqueKey in childrel is
+ * still valid in parent level, but we need convert the format from child expr to
+ * parent expr.
+ */
+ if (list_length(childrels) == 1)
+ {
+ /* Check for Rule 1 */
+ RelOptInfo *childrel = linitial_node(RelOptInfo, childrels);
+ ListCell *lc;
+ Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ if (relation_is_onerow(childrel))
+ {
+ add_uniquekey_for_onerow(rel);
+ return;
+ }
+
+ foreach(lc, childrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, childrel->relid);
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+ ListCell *lc2;
+ foreach(lc2, ukey->exprs)
+ {
+ Var *var = (Var *)lfirst(lc2);
+ /*
+ * If the expr comes from a expression, it is hard to build the expression
+ * in parent so ignore that case for now.
+ */
+ if(!IsA(var, Var))
+ {
+ can_reuse = false;
+ break;
+ }
+ /* Convert it to parent var */
+ parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, var));
+ }
+ if (can_reuse)
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
+ }
+ }
+ else
+ {
+ /* Check for rule 2 */
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
+ {
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ continue;
+
+ /*
+ * During simple_copy_indexinfo_to_parent, we need to convert var from
+ * child var to parent var, index on expression is too complex to handle.
+ * so ignore it for now.
+ */
+ if (ind->indexprs != NIL)
+ continue;
+
+ modified_index = simple_copy_indexinfo_to_parent(root, rel, ind);
+ /*
+ * If the unique index doesn't contain partkey, then it is unique
+ * on this partition only, so it is useless for us.
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
+
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ if (global_uniq_indexlist != NIL)
+ {
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+ if (is_first)
+ {
+ is_first = false;
+ continue;
+ }
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+ }
+ /* Now we have a list of unique index which are exactly same on all childrels,
+ * Set the UniqueKey just like it is non-partition table
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
+ }
+ }
+}
+
+
+/*
+ * populate_distinctrel_uniquekeys
+ */
+void
+populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel)
+{
+ /* The unique key before the distinct is still valid. */
+ distinctrel->uniquekeys = list_copy(inputrel->uniquekeys);
+ add_uniquekey_from_sortgroups(root, distinctrel, root->parse->distinctClause);
+}
+
+/*
+ * populate_grouprel_uniquekeys
+ */
+void
+populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel)
+
+{
+ Query *parse = root->parse;
+ bool input_ukey_added = false;
+ ListCell *lc;
+
+ if (relation_is_onerow(inputrel))
+ {
+ add_uniquekey_for_onerow(grouprel);
+ return;
+ }
+ if (parse->groupingSets)
+ return;
+
+ /* A Normal group by without grouping set. */
+ if (parse->groupClause)
+ {
+ /*
+ * Current even the groupby clause is Unique already, but if query has aggref
+ * We have to create grouprel still. To keep the UnqiueKey short, we will check
+ * the UniqueKey of input_rel still valid, if so we reuse it.
+ */
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ {
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys,
+ ukey);
+ input_ukey_added = true;
+ }
+ }
+ if (!input_ukey_added)
+ /*
+ * group by clause must be a super-set of grouprel->reltarget->exprs except the
+ * aggregation expr, so if such exprs is unique already, no bother to generate
+ * new uniquekey for group by exprs.
+ */
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
+ }
+ else
+ /* It has aggregation but without a group by, so only one row returned */
+ add_uniquekey_for_onerow(grouprel);
+}
+
+/*
+ * simple_copy_uniquekeys
+ * Using a function for the one-line code makes us easy to check where we simply
+ * copied the uniquekey.
+ */
+void
+simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel)
+{
+ newrel->uniquekeys = oldrel->uniquekeys;
+}
+
+/*
+ * populate_unionrel_uniquekeys
+ */
+void
+populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel)
+{
+ ListCell *lc;
+ List *exprs = NIL;
+
+ Assert(unionrel->uniquekeys == NIL);
+
+ foreach(lc, unionrel->reltarget->exprs)
+ {
+ exprs = lappend(exprs, lfirst(lc));
+ }
+
+ if (exprs == NIL)
+ /* SQL: select union select; is valid, we need to handle it here. */
+ add_uniquekey_for_onerow(unionrel);
+ else
+ unionrel->uniquekeys = lappend(unionrel->uniquekeys,
+ makeUniqueKey(exprs,false));
+
+}
+
+/*
+ * populate_joinrel_uniquekeys
+ *
+ * populate uniquekeys for joinrel. We will check each relation to see if its
+ * UniqueKey is still valid via innerrel_keeps_unique, if so, we add it to
+ * joinrel. The multi_nullvals field will be changed to true for some outer
+ * join cases and one-row UniqueKey needs to be converted to normal UniqueKey
+ * for the same case as well.
+ * For the uniquekey in either baserel which can't be unique after join, we still
+ * check to see if combination of UniqueKeys from both side is still useful for us.
+ * if yes, we add it to joinrel as well.
+ */
+void
+populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *outerrel, RelOptInfo *innerrel,
+ List *restrictlist, JoinType jointype)
+{
+ ListCell *lc, *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow, outer_onerow;
+ bool mergejoin_allowed;
+
+ /* Care about the outerrel relation only for SEMI/ANTI join */
+ if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
+ {
+ foreach(lc, outerrel->uniquekeys)
+ {
+ UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+ if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
+ }
+ return;
+ }
+
+ Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
+
+ /* Fast path */
+ if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
+ return;
+
+ inner_onerow = relation_is_onerow(innerrel);
+ outer_onerow = relation_is_onerow(outerrel);
+
+ outerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initililze_uniquecontext_for_joinrel(innerrel);
+
+ clause_list = select_mergejoin_clauses(root, joinrel, outerrel, innerrel,
+ restrictlist, jointype,
+ &mergejoin_allowed);
+
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true /* reverse */))
+ {
+ bool outer_impact = jointype == JOIN_FULL;
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ /* Outer relation has one row, and the unique key is not duplicated after join,
+ * the joinrel will still has one row unless the jointype == JOIN_FULL.
+ */
+ if (outer_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (outer_onerow)
+ {
+ /*
+ * The onerow outerrel becomes multi rows and multi_nullvals
+ * will be changed to true. We also need to set the exprs correctly since it
+ * can't be NIL any more.
+ */
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, outerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Change multi_nullvals to true due to the full join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs, true));
+ else
+ /* Just reuse it */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
+ {
+ bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+
+ foreach(lc, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+
+ if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
+ {
+ ctx->useful = false;
+ continue;
+ }
+
+ if (inner_onerow && !outer_impact)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return;
+ }
+ else if (inner_onerow)
+ {
+ ListCell *lc2;
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, innerrel, NULL))
+ {
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(lfirst(lc2), true));
+ }
+ }
+ else
+ {
+ if (!ctx->uniquekey->multi_nullvals && outer_impact)
+ /* Need to change multi_nullvals to true due to the outer join. */
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(ctx->uniquekey->exprs,
+ true));
+ else
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ ctx->uniquekey);
+
+ }
+ ctx->added_to_joinrel = true;
+ }
+ }
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well regardless
+ * of join type, but no bother to add it if its subset has been added to joinrel
+ * already or it is not useful for the joinrel.
+ */
+ foreach(lc, outerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+ if (ctx1->added_to_joinrel || !ctx1->useful)
+ continue;
+ foreach(lc2, innerrel_ukey_ctx)
+ {
+ UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+ if (ctx2->added_to_joinrel || !ctx2->useful)
+ continue;
+ if (add_combined_uniquekey(root, joinrel, outerrel, innerrel,
+ ctx1->uniquekey, ctx2->uniquekey,
+ jointype))
+ /* If we set a onerow UniqueKey to joinrel, we don't need other. */
+ return;
+ }
+ }
+}
+
+
+/*
+ * convert_subquery_uniquekeys
+ *
+ * Covert the UniqueKey in subquery to outer relation.
+ */
+void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel)
+{
+ ListCell *lc;
+
+ if (sub_final_rel->uniquekeys == NIL)
+ return;
+
+ if (relation_is_onerow(sub_final_rel))
+ {
+ add_uniquekey_for_onerow(currel);
+ return;
+ }
+
+ Assert(currel->subroot != NULL);
+
+ foreach(lc, sub_final_rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ ListCell *lc;
+ List *exprs = NIL;
+ bool ukey_useful = true;
+
+ /* One row case is handled above */
+ Assert(ukey->exprs != NIL);
+ foreach(lc, ukey->exprs)
+ {
+ Var *var;
+ TargetEntry *tle = tlist_member(lfirst(lc),
+ currel->subroot->processed_tlist);
+ if (tle == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ var = find_var_for_subquery_tle(currel, tle);
+ if (var == NULL)
+ {
+ ukey_useful = false;
+ break;
+ }
+ exprs = lappend(exprs, var);
+ }
+
+ if (ukey_useful)
+ currel->uniquekeys = lappend(currel->uniquekeys,
+ makeUniqueKey(exprs,
+ ukey->multi_nullvals));
+
+ }
+}
+
+/*
+ * innerrel_keeps_unique
+ *
+ * Check if Unique key of the innerrel is valid after join. innerrel's UniqueKey
+ * will be still valid if innerrel's any-column mergeop outrerel's uniquekey
+ * exists in clause_list.
+ *
+ * Note: the clause_list must be a list of mergeable restrictinfo already.
+ */
+static bool
+innerrel_keeps_unique(PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *clause_list,
+ bool reverse)
+{
+ ListCell *lc, *lc2, *lc3;
+
+ if (outerrel->uniquekeys == NIL || innerrel->uniquekeys == NIL)
+ return false;
+
+ /* Check if there is outerrel's uniquekey in mergeable clause. */
+ foreach(lc, outerrel->uniquekeys)
+ {
+ List *outer_uq_exprs = lfirst_node(UniqueKey, lc)->exprs;
+ bool clauselist_matchs_all_exprs = true;
+ foreach(lc2, outer_uq_exprs)
+ {
+ Node *outer_uq_expr = lfirst(lc2);
+ bool find_uq_expr_in_clauselist = false;
+ foreach(lc3, clause_list)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc3);
+ Node *outer_expr;
+ if (reverse)
+ outer_expr = rinfo->outer_is_left ? get_rightop(rinfo->clause) : get_leftop(rinfo->clause);
+ else
+ outer_expr = rinfo->outer_is_left ? get_leftop(rinfo->clause) : get_rightop(rinfo->clause);
+ if (equal(outer_expr, outer_uq_expr))
+ {
+ find_uq_expr_in_clauselist = true;
+ break;
+ }
+ }
+ if (!find_uq_expr_in_clauselist)
+ {
+ /* No need to check the next exprs in the current uniquekey */
+ clauselist_matchs_all_exprs = false;
+ break;
+ }
+ }
+
+ if (clauselist_matchs_all_exprs)
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * relation_is_onerow
+ * Check if it is a one-row relation by checking UniqueKey.
+ */
+bool
+relation_is_onerow(RelOptInfo *rel)
+{
+ UniqueKey *ukey;
+ if (rel->uniquekeys == NIL)
+ return false;
+ ukey = linitial_node(UniqueKey, rel->uniquekeys);
+ return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+}
+
+/*
+ * relation_has_uniquekeys_for
+ * Returns true if we have proofs that 'rel' cannot return multiple rows with
+ * the same values in each of 'exprs'. Otherwise returns false.
+ */
+bool
+relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
+ List *exprs, bool allow_multinulls)
+{
+ ListCell *lc;
+
+ /*
+ * For UniqueKey->onerow case, the uniquekey->exprs is empty as well
+ * so we can't rely on list_is_subset to handle this special cases
+ */
+ if (exprs == NIL)
+ return false;
+
+ foreach(lc, rel->uniquekeys)
+ {
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+ if (ukey->multi_nullvals && !allow_multinulls)
+ continue;
+ if (list_is_subset(ukey->exprs, exprs))
+ return true;
+ }
+ return false;
+}
+
+
+/*
+ * get_exprs_from_uniqueindex
+ *
+ * Return a list of exprs which is unique. set useful to false if this
+ * unique index is not useful for us.
+ */
+static List *
+get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
+ List *const_exprs,
+ List *const_expr_opfamilies,
+ Bitmapset *used_varattrs,
+ bool *useful,
+ bool *multi_nullvals)
+{
+ List *exprs = NIL;
+ ListCell *indexpr_item;
+ int c = 0;
+
+ *useful = true;
+ *multi_nullvals = false;
+
+ indexpr_item = list_head(unique_index->indexprs);
+ for(c = 0; c < unique_index->ncolumns; c++)
+ {
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
+
+ if(attr > 0)
+ {
+ expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
+ }
+ else if (attr == 0)
+ {
+ /* Expression index */
+ expr = lfirst(indexpr_item);
+ indexpr_item = lnext(unique_index->indexprs, indexpr_item);
+ }
+ else /* attr < 0 */
+ {
+ /* Index on system column is not supported */
+ Assert(false);
+ }
+
+ /*
+ * Check index_col = Const case with regarding to opfamily checking
+ * If we can remove the index_col from the final UniqueKey->exprs.
+ */
+ forboth(lc1, const_exprs, lc2, const_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;
+
+ /* Check if the indexed expr is used in rel */
+ if (attr > 0)
+ {
+ /*
+ * Normal Indexed column, if the col is not used, then the index is useless
+ * for uniquekey.
+ */
+ attr -= FirstLowInvalidHeapAttributeNumber;
+
+ if (!bms_is_member(attr, used_varattrs))
+ {
+ *useful = false;
+ break;
+ }
+ }
+ else if (!list_member(unique_index->rel->reltarget->exprs, expr))
+ {
+ /* Expression index but the expression is not used in rel */
+ *useful = false;
+ break;
+ }
+
+ /* check not null property. */
+ if (attr == 0)
+ {
+ /* We never know if a expression yields null or not */
+ *multi_nullvals = true;
+ }
+ else if (!bms_is_member(attr, unique_index->rel->notnullattrs)
+ && !bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ unique_index->rel->notnullattrs))
+ {
+ *multi_nullvals = true;
+ }
+
+ exprs = lappend(exprs, expr);
+ }
+ return exprs;
+}
+
+
+/*
+ * add_uniquekey_for_onerow
+ * If we are sure that the relation only returns one row, then all the columns
+ * are unique. However we don't need to create UniqueKey for every column, we
+ * just set exprs = NIL and overwrites all the other UniqueKey on this RelOptInfo
+ * since this one has strongest semantics.
+ */
+void
+add_uniquekey_for_onerow(RelOptInfo *rel)
+{
+ /*
+ * We overwrite the previous UniqueKey on purpose since this one has the
+ * strongest semantic.
+ */
+ rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
+}
+
+
+/*
+ * initililze_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel
+ */
+static List *
+initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+{
+ List *res = NIL;
+ ListCell *lc;
+ foreach(lc, inputrel->uniquekeys)
+ {
+ UniqueKeyContext context;
+ context = palloc(sizeof(struct UniqueKeyContextData));
+ context->uniquekey = lfirst_node(UniqueKey, lc);
+ context->added_to_joinrel = false;
+ context->useful = true;
+ res = lappend(res, context);
+ }
+ return res;
+}
+
+
+/*
+ * get_exprs_from_uniquekey
+ * Unify the way of get List of exprs from a one-row UniqueKey or
+ * normal UniqueKey. for the onerow case, every expr in rel1 is a valid
+ * UniqueKey. Return a List of exprs.
+ *
+ * rel1: The relation which you want to get the exprs.
+ * ukey: The UniqueKey you want to get the exprs.
+ */
+static List *
+get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
+ RelOptInfo *rel1, UniqueKey *ukey)
+{
+ ListCell *lc;
+ bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+
+ List *res = NIL;
+ Assert(onerow || ukey);
+ if (onerow)
+ {
+ /* Only cares about the exprs still exist in joinrel */
+ foreach(lc, joinrel->reltarget->exprs)
+ {
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+ if (bms_is_subset(relids, rel1->relids))
+ {
+ res = lappend(res, list_make1(lfirst(lc)));
+ }
+ }
+ }
+ else
+ {
+ res = list_make1(ukey->exprs);
+ }
+ return res;
+}
+
+/*
+ * Partitioned table Unique Keys.
+ * The partition table unique key is maintained as:
+ * 1. The index must be unique as usual.
+ * 2. The index must contains partition key.
+ * 3. The index must exist on all the child rel. see simple_indexinfo_equal for
+ * how we compare it.
+ */
+
+/*
+ * index_constains_partkey
+ * return true if the index contains the partiton key.
+ */
+static bool
+index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+{
+ ListCell *lc;
+ int i;
+ Assert(IS_PARTITIONED_REL(partrel));
+ Assert(partrel->part_scheme->partnatts > 0);
+
+ for(i = 0; i < partrel->part_scheme->partnatts; i++)
+ {
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+ foreach(lc, ind->indextlist)
+ {
+ Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ if (equal(index_expr, part_expr))
+ {
+ found_in_index = true;
+ break;
+ }
+ }
+ if (!found_in_index)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * simple_indexinfo_equal
+ *
+ * Used to check if the 2 index is same as each other. The index here
+ * is COPIED from childrel and did some tiny changes(see
+ * simple_copy_indexinfo_to_parent)
+ */
+static bool
+simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
+{
+ Size oid_cmp_len = sizeof(Oid) * ind1->ncolumns;
+
+ return ind1->ncolumns == ind2->ncolumns &&
+ ind1->unique == ind2->unique &&
+ memcmp(ind1->indexkeys, ind2->indexkeys, sizeof(int) * ind1->ncolumns) == 0 &&
+ memcmp(ind1->opfamily, ind2->opfamily, oid_cmp_len) == 0 &&
+ memcmp(ind1->opcintype, ind2->opcintype, oid_cmp_len) == 0 &&
+ memcmp(ind1->sortopfamily, ind2->sortopfamily, oid_cmp_len) == 0 &&
+ equal(get_tlist_exprs(ind1->indextlist, true),
+ get_tlist_exprs(ind2->indextlist, true));
+}
+
+
+/*
+ * The below macros are used for simple_copy_indexinfo_to_parent which is so
+ * customized that I don't want to put it to copyfuncs.c. So copy it here.
+ */
+#define COPY_POINTER_FIELD(fldname, sz) \
+ do { \
+ Size _size = (sz); \
+ newnode->fldname = palloc(_size); \
+ memcpy(newnode->fldname, from->fldname, _size); \
+ } while (0)
+
+#define COPY_NODE_FIELD(fldname) \
+ (newnode->fldname = copyObjectImpl(from->fldname))
+
+#define COPY_SCALAR_FIELD(fldname) \
+ (newnode->fldname = from->fldname)
+
+
+/*
+ * simple_copy_indexinfo_to_parent (from partition)
+ * Copy the IndexInfo from child relation to parent relation with some modification,
+ * which is used to test:
+ * 1. If the same index exists in all the childrels.
+ * 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ */
+static IndexOptInfo *
+simple_copy_indexinfo_to_parent(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ IndexOptInfo *from)
+{
+ IndexOptInfo *newnode = makeNode(IndexOptInfo);
+ AppendRelInfo *appinfo = find_appinfo_by_child(root, from->rel->relid);
+ ListCell *lc;
+ int idx = 0;
+
+ COPY_SCALAR_FIELD(ncolumns);
+ COPY_SCALAR_FIELD(nkeycolumns);
+ COPY_SCALAR_FIELD(unique);
+ COPY_SCALAR_FIELD(immediate);
+ /* We just need to know if it is NIL or not */
+ COPY_SCALAR_FIELD(indpred);
+ COPY_SCALAR_FIELD(predOK);
+ COPY_POINTER_FIELD(indexkeys, from->ncolumns * sizeof(int));
+ COPY_POINTER_FIELD(indexcollations, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opfamily, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(opcintype, from->ncolumns * sizeof(Oid));
+ COPY_POINTER_FIELD(sortopfamily, from->ncolumns * sizeof(Oid));
+ COPY_NODE_FIELD(indextlist);
+
+ /* Convert index exprs on child expr to expr on parent */
+ foreach(lc, newnode->indextlist)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+ /* Index on expression is ignored */
+ Assert(IsA(tle->expr, Var));
+ tle->expr = (Expr *) find_parent_var(appinfo, (Var *) tle->expr);
+ newnode->indexkeys[idx] = castNode(Var, tle->expr)->varattno;
+ idx++;
+ }
+ newnode->rel = parentrel;
+ return newnode;
+}
+
+/*
+ * adjust_partition_unique_indexlist
+ *
+ * global_unique_indexes: At the beginning, it contains the copy & modified
+ * unique index from the first partition. And then check if each index in it still
+ * exists in the following partitions. If no, remove it. at last, it has an
+ * index list which exists in all the partitions.
+ */
+static void
+adjust_partition_unique_indexlist(PlannerInfo *root,
+ RelOptInfo *parentrel,
+ RelOptInfo *childrel,
+ List **global_unique_indexes)
+{
+ ListCell *lc, *lc2;
+ foreach(lc, *global_unique_indexes)
+ {
+ IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
+ bool found_in_child = false;
+
+ foreach(lc2, childrel->indexlist)
+ {
+ IndexOptInfo *p_ind = lfirst_node(IndexOptInfo, lc2);
+ IndexOptInfo *p_ind_copy;
+ if (!p_ind->unique || !p_ind->immediate ||
+ (p_ind->indpred != NIL && !p_ind->predOK))
+ continue;
+ p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+ if (simple_indexinfo_equal(p_ind_copy, g_ind))
+ {
+ found_in_child = true;
+ break;
+ }
+ }
+ if (!found_in_child)
+ /* The index doesn't exist in childrel, remove it from global_unique_indexes */
+ *global_unique_indexes = foreach_delete_current(*global_unique_indexes, lc);
+ }
+}
+
+/* Helper function for groupres/distinctrel */
+static void
+add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
+{
+ Query *parse = root->parse;
+ List *exprs;
+
+ /*
+ * XXX: If there are some vars which is not in current levelsup, the semantic is
+ * imprecise, should we avoid it or not? levelsup = 1 is just a demo, maybe we need to
+ * check every level other than 0, if so, looks we have to write another
+ * pull_var_walker.
+ */
+ List *upper_vars = pull_vars_of_level((Node*)sortgroups, 1);
+
+ if (upper_vars != NIL)
+ return;
+
+ exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(exprs,
+ false /* sortgroupclause can't be multi_nullvals */));
+}
+
+
+/*
+ * add_combined_uniquekey
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
+ * the jointype.
+ */
+bool
+add_combined_uniquekey(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outer_rel,
+ RelOptInfo *inner_rel,
+ UniqueKey *outer_ukey,
+ UniqueKey *inner_ukey,
+ JoinType jointype)
+{
+
+ ListCell *lc1, *lc2;
+
+ /* Either side has multi_nullvals or we have outer join,
+ * the combined UniqueKey has multi_nullvals */
+ bool multi_nullvals = outer_ukey->multi_nullvals ||
+ inner_ukey->multi_nullvals || IS_OUTER_JOIN(jointype);
+
+ /* The only case we can get onerow joinrel after join */
+ if (relation_is_onerow(outer_rel)
+ && relation_is_onerow(inner_rel)
+ && jointype == JOIN_INNER)
+ {
+ add_uniquekey_for_onerow(joinrel);
+ return true;
+ }
+
+ foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
+ {
+ foreach(lc2, get_exprs_from_uniquekey(root, joinrel, inner_rel, inner_ukey))
+ {
+ List *exprs = list_concat_copy(lfirst_node(List, lc1), lfirst_node(List, lc2));
+ joinrel->uniquekeys = lappend(joinrel->uniquekeys,
+ makeUniqueKey(exprs,
+ multi_nullvals));
+ }
+ }
+ return false;
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 424d25cbd5..8d8e493f5c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ simple_copy_uniquekeys(current_rel, final_rel);
+
/*
* Generate partial paths for final_rel, too, if outer query levels might
* be able to make use of them.
@@ -3899,6 +3901,8 @@ create_grouping_paths(PlannerInfo *root,
}
set_cheapest(grouped_rel);
+
+ populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
return grouped_rel;
}
@@ -4621,7 +4625,7 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
-
+ simple_copy_uniquekeys(input_rel, window_rel);
return window_rel;
}
@@ -4935,7 +4939,7 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
-
+ populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
return distinct_rel;
}
@@ -5196,6 +5200,8 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ simple_copy_uniquekeys(input_rel, ordered_rel);
+
return ordered_rel;
}
@@ -6073,6 +6079,9 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
if (list_length(targets) == 1)
return;
+ /* UniqueKey is not valid after handling the SRF. */
+ rel->uniquekeys = NIL;
+
/*
* Stack SRF-evaluation nodes atop each path for the rel.
*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index becdcbb872..b7626545bf 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -689,6 +689,8 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
+ /* Add the UniqueKeys */
+ populate_unionrel_uniquekeys(root, result_rel);
return result_rel;
}
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 86922a273c..6817c9c787 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -746,3 +746,47 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
+
+/*
+ * find_appinfo_by_child
+ *
+ */
+AppendRelInfo *
+find_appinfo_by_child(PlannerInfo *root, Index child_index)
+{
+ ListCell *lc;
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+ if (appinfo->child_relid == child_index)
+ return appinfo;
+ }
+ elog(ERROR, "parent relation cant be found");
+ return NULL;
+}
+
+/*
+ * find_parent_var
+ *
+ */
+Var *
+find_parent_var(AppendRelInfo *appinfo, Var *child_var)
+{
+ ListCell *lc;
+ Var *res = NULL;
+ Index attno = 1;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *child_node = lfirst(lc);
+ if (equal(child_node, child_var))
+ {
+ res = copyObject(child_var);
+ res->varattno = attno;
+ res->varno = appinfo->parent_relid;
+ }
+ attno++;
+ }
+ if (res == NULL)
+ elog(ERROR, "parent var can't be found.");
+ return res;
+}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index be1c9ddd96..3eec1f4d74 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -736,6 +736,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
{
Node *onecq = (Node *) lfirst(lc2);
bool pseudoconstant;
+ RestrictInfo *child_rinfo;
/* check for pseudoconstant (no Vars or volatile functions) */
pseudoconstant =
@@ -747,14 +748,15 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
root->hasPseudoConstantQuals = true;
}
/* reconstitute RestrictInfo with appropriate properties */
- childquals = lappend(childquals,
- make_restrictinfo(root,
- (Expr *) onecq,
- rinfo->is_pushed_down,
- rinfo->outerjoin_delayed,
- pseudoconstant,
- rinfo->security_level,
- NULL, NULL, NULL));
+ child_rinfo = make_restrictinfo(root,
+ (Expr *) onecq,
+ rinfo->is_pushed_down,
+ rinfo->outerjoin_delayed,
+ pseudoconstant,
+ rinfo->security_level,
+ NULL, NULL, NULL);
+ child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
+ childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
cq_min_security = Min(cq_min_security, rinfo->security_level);
}
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..4fe1824eb0 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -16,6 +16,7 @@
#include "nodes/execnodes.h"
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
extern A_Expr *makeA_Expr(A_Expr_Kind kind, List *name,
@@ -106,4 +107,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern UniqueKey* makeUniqueKey(List *exprs, bool multi_nullvals);
+
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..3320273ac1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
T_EquivalenceMember,
T_PathKey,
T_PathTarget,
+ T_UniqueKey,
T_RestrictInfo,
T_IndexClause,
T_PlaceHolderVar,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0d61f04d27..80561b1003 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -739,6 +739,7 @@ typedef struct RelOptInfo
QualCost baserestrictcost; /* cost of evaluating the above */
Index baserestrict_min_security; /* min security_level found in
* baserestrictinfo */
+ List *uniquekeys; /* List of UniqueKey */
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
@@ -1059,6 +1060,28 @@ typedef struct PathKey
} PathKey;
+/*
+ * UniqueKey
+ *
+ * Represents the unique properties held by a RelOptInfo.
+ *
+ * exprs is a list of exprs which is unique on current RelOptInfo. exprs = NIL
+ * is a special case of UniqueKey, which means there is only 1 row in that
+ * relation.
+ * multi_nullvals: true means multi null values may exist in these exprs, so the
+ * uniqueness is not guaranteed in this case. This field is necessary for
+ * remove_useless_join & reduce_unique_semijoins where we don't mind these
+ * duplicated NULL values. It is set to true for 2 cases. One is a unique key
+ * from a unique index but the related column is nullable. The other one is for
+ * outer join. see populate_joinrel_uniquekeys for detail.
+ */
+typedef struct UniqueKey
+{
+ NodeTag type;
+ List *exprs;
+ bool multi_nullvals;
+} UniqueKey;
+
/*
* PathTarget
*
@@ -2496,7 +2519,7 @@ typedef enum
*
* flags indicating what kinds of grouping are possible.
* partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * have been initialized.
+ * have been initialized.
* agg_partial_costs gives partial aggregation costs.
* agg_final_costs gives finalization costs.
* target_parallel_safe is true if target is parallel safe.
@@ -2526,8 +2549,8 @@ typedef struct
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate.
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
- * expressions computed by preprocess_limit() (see comments for
- * preprocess_limit() for more information).
+ * expressions computed by preprocess_limit() (see comments for
+ * preprocess_limit() for more information).
*/
typedef struct
{
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 404e03f132..85c65e910c 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -566,6 +566,16 @@ extern pg_nodiscard List *list_delete_first(List *list);
extern pg_nodiscard List *list_delete_last(List *list);
extern pg_nodiscard List *list_delete_nth_cell(List *list, int n);
extern pg_nodiscard List *list_delete_cell(List *list, ListCell *cell);
+extern bool list_is_subset(const List *members, const List *target);
+
+extern List *list_delete(List *list, void *datum);
+extern List *list_delete_ptr(List *list, void *datum);
+extern List *list_delete_int(List *list, int datum);
+extern List *list_delete_oid(List *list, Oid datum);
+extern List *list_delete_first(List *list);
+extern List *list_delete_last(List *list);
+extern List *list_delete_nth_cell(List *list, int n);
+extern List *list_delete_cell(List *list, ListCell *cell);
extern List *list_union(const List *list1, const List *list2);
extern List *list_union_ptr(const List *list1, const List *list2);
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index 4cbf8c26cc..43b8d05f69 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -32,4 +32,7 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
+extern AppendRelInfo *find_appinfo_by_child(PlannerInfo *root, Index child_index);
+extern Var *find_parent_var(AppendRelInfo *appinfo, Var *child_var);
+
#endif /* APPENDINFO_H */
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index d587952b7d..843aafa51e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
#define OPTIMIZER_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/* Test if an expression node represents a SRF call. Beware multiple eval! */
#define IS_SRF_CALL(node) \
@@ -168,6 +169,7 @@ extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
List *targetList);
+extern Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle);
extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 035d3e1206..1adf99c9ee 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -247,5 +247,48 @@ extern PathKey *make_canonical_pathkey(PlannerInfo *root,
int strategy, bool nulls_first);
extern void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels);
+extern List *select_mergejoin_clauses(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ List *restrictlist,
+ JoinType jointype,
+ bool *mergejoin_allowed);
+
+/*
+ * uniquekeys.c
+ * Utilities for matching and building unique keys
+ */
+extern void populate_baserel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List* unique_index_list);
+extern void populate_partitionedrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *childrels);
+extern void populate_distinctrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *inputrel,
+ RelOptInfo *distinctrel);
+extern void populate_grouprel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *grouprel,
+ RelOptInfo *inputrel);
+extern void populate_unionrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *unionrel);
+extern void simple_copy_uniquekeys(RelOptInfo *oldrel,
+ RelOptInfo *newrel);
+extern void convert_subquery_uniquekeys(PlannerInfo *root,
+ RelOptInfo *currel,
+ RelOptInfo *sub_final_rel);
+extern void populate_joinrel_uniquekeys(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *rel1,
+ RelOptInfo *rel2,
+ List *restrictlist,
+ JoinType jointype);
+
+extern bool relation_has_uniquekeys_for(PlannerInfo *root,
+ RelOptInfo *rel,
+ List *exprs,
+ bool allow_multinulls);
+extern bool relation_is_onerow(RelOptInfo *rel);
#endif /* PATHS_H */
--
2.26.2
--yh7cw345mbttadjz
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="v38-0003-Extend-UniqueKeys.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Making JIT more granular
@ 2022-04-26 05:24 David Rowley <[email protected]>
2022-05-14 00:35 ` Re: Making JIT more granular Andy Fan <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: David Rowley @ 2022-04-26 05:24 UTC (permalink / raw)
To: PostgreSQL Developers <[email protected]>; Andres Freund <[email protected]>; Magnus Hagander <[email protected]>
(This is an old thread. See [1] if you're missing the original email.)
On Tue, 4 Aug 2020 at 14:01, David Rowley <[email protected]> wrote:
> At the moment JIT compilation, if enabled, is applied to all
> expressions in the entire plan. This can sometimes be a problem as
> some expressions may be evaluated lots and warrant being JITted, but
> others may only be evaluated just a few times, or even not at all.
>
> This problem tends to become large when table partitioning is involved
> as the number of expressions in the plan grows with each partition
> present in the plan. Some partitions may have many rows and it can be
> useful to JIT expression, but others may have few rows or even no
> rows, in which case JIT is a waste of effort.
This patch recently came up again in [2], where Magnus proposed we add
a new GUC [3] to warn users when JIT compilation takes longer than the
specified fraction of execution time. Over there I mentioned that I
think it might be better to have a go at making the JIT costing better
so that it's more aligned to the amount of JITing work there is to do
rather than the total cost of the plan without any consideration about
how much there is to JIT compile.
In [4], Andres reminded me that I need to account for the number of
times a given plan is (re)scanned rather than just the total_cost of
the Plan node. There followed some discussion about how that might be
done.
I've loosely implemented this in the attached patch. In order to get
the information about the expected number of "loops" a given Plan node
will be subject to, I've modified create_plan() so that it passes this
value down recursively while creating the plan. Nodes such as Nested
Loop multiply the "est_calls" by the number of outer rows. For nodes
such as Material, I've made the estimated calls = 1.0. Memoize must
take into account the expected cache hit ratio, which I've had to
record as part of MemoizePath so that create_plan knows about that.
Altogether, this is a fair bit of churn for createplan.c, and it's
still only part of the way there. When planning subplans, we do
create_plan() right away and since we plan subplans before the outer
plans, we've no idea how many times the subplan will be rescanned. So
to make this work fully I think we'd need to modify the planner so
that we delay the create_plan() for subplans until sometime after
we've planned the outer query.
The reason that I'm posting about this now is mostly because I did say
I'd come back to this patch for v16 and I'm also feeling bad that I
-1'd Magnus' patch, which likely resulted in making zero forward
progress in improving JIT and it's costing situation for v15.
The reason I've not completed this patch to fix the deficiencies
regarding subplans is that that's quite a bit of work and I don't
really want to do that right now. We might decide that JIT costing
should work in a completely different way that does not require
estimating how many times a plan node will be rescanned. I think
there's enough patch here to allow us to test this and then decide if
it's any good or not.
There's also maybe some controversy in the patch. I ended up modifying
EXPLAIN so that it shows loops=N as part of the estimated costs. I
understand there's likely to be fallout from doing that as there are
various tools around that this would likely break. I added that for a
couple of reasons; 1) I think it would be tricky to figure out why JIT
was or was not enabled without showing that in EXPLAIN, and; 2) I
needed to display it somewhere for my testing so I could figure out if
I'd done something wrong when calculating the value during
create_plan().
This basically looks like:
postgres=# explain select * from h, h h1, h h2;
QUERY PLAN
--------------------------------------------------------------------------
Nested Loop (cost=0.00..12512550.00 rows=1000000000 width=12)
-> Nested Loop (cost=0.00..12532.50 rows=1000000 width=8)
-> Seq Scan on h (cost=0.00..15.00 rows=1000 width=4)
-> Materialize (cost=0.00..20.00 rows=1000 width=4 loops=1000)
-> Seq Scan on h h1 (cost=0.00..15.00 rows=1000 width=4)
-> Materialize (cost=0.00..20.00 rows=1000 width=4 loops=1000000)
-> Seq Scan on h h2 (cost=0.00..15.00 rows=1000 width=4)
(7 rows)
Just the same as EXPLAIN ANALYZE, I've coded loops= to only show when
there's more than 1 loop. You can also see that the node below
Materialize is not expected to be scanned multiple times. Technically
it could when a parameter changes, but right now it seems more trouble
than it's worth to go to the trouble of estimating that during
create_plan(). There's also some variation from the expected loops and
the actual regarding parallel workers. In the estimate, this is just
the number of times an average worker is expected to invoke the plan,
whereas the actual "loops" is the sum of each worker's invocations.
The other slight controversy that I can see in the patch is
repurposing the JIT cost GUCs and giving them a completely different
meaning than they had previously. I've left them as-is for now as I
didn't think renaming GUCs would ease the pain that DBAs would have to
endure as a result of this change.
Does anyone have any thoughts about this JIT costing? Is this an
improvement? Is there a better way?
David
[1] https://www.postgresql.org/message-id/[email protected]...
[2] https://www.postgresql.org/message-id/CAApHDvrEoQ5p61NjDCKVgEWaH0qm1KprYw2-7m8-6ZGGJ8A2Dw%40mail.gma...
[3] https://www.postgresql.org/message-id/CABUevExR_9ZmkYj-aBvDreDKUinWLBBpORcmTbuPdNb5vGOLtA%40mail.gma...
[4] https://www.postgresql.org/message-id/20220329231641.ai3qrzpdo2vqvwix%40alap3.anarazel.de
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 4773cadec0..ba52b48783 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -129,7 +129,8 @@ static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
- Plan *outer_plan);
+ Plan *outer_plan,
+ double est_calls);
static void fileExplainForeignScan(ForeignScanState *node, ExplainState *es);
static void fileBeginForeignScan(ForeignScanState *node, int eflags);
static TupleTableSlot *fileIterateForeignScan(ForeignScanState *node);
@@ -588,7 +589,8 @@ fileGetForeignPlan(PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
- Plan *outer_plan)
+ Plan *outer_plan,
+ double est_calls)
{
Index scan_relid = baserel->relid;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 0e5771c89d..f51bf8a649 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -335,7 +335,8 @@ static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
- Plan *outer_plan);
+ Plan *outer_plan,
+ double est_calls);
static void postgresBeginForeignScan(ForeignScanState *node, int eflags);
static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node);
static void postgresReScanForeignScan(ForeignScanState *node);
@@ -1221,7 +1222,8 @@ postgresGetForeignPlan(PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
- Plan *outer_plan)
+ Plan *outer_plan,
+ double est_calls)
{
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
Index scan_relid;
@@ -1385,7 +1387,8 @@ postgresGetForeignPlan(PlannerInfo *root,
* a Result node atop the plan tree.
*/
outer_plan = change_plan_targetlist(outer_plan, fdw_scan_tlist,
- best_path->path.parallel_safe);
+ best_path->path.parallel_safe,
+ est_calls);
}
}
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 03986946a8..643680bbc6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5603,8 +5603,11 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
</term>
<listitem>
<para>
- Sets the query cost above which JIT compilation is activated, if
- enabled (see <xref linkend="jit"/>).
+ Sets the cost threshold for which a given plan node will consider
+ performing JIT compilation for itself. The <literal>total_cost</literal>
+ of a given plan node multiplied by its estimated <literal>loops</literal>
+ must exceed this value before JIT compilation is considered for the
+ plan node xref linkend="jit"/> must also be enabled.
Performing <acronym>JIT</acronym> costs planning time but can
accelerate query execution.
Setting this to <literal>-1</literal> disables JIT compilation.
@@ -5621,12 +5624,13 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
</term>
<listitem>
<para>
- Sets the query cost above which JIT compilation attempts to inline
- functions and operators. Inlining adds planning time, but can
- improve execution speed. It is not meaningful to set this to less
- than <varname>jit_above_cost</varname>.
- Setting this to <literal>-1</literal> disables inlining.
- The default is <literal>500000</literal>.
+ Sets the overall plan node cost above which JIT compilation attempts to
+ inline functions and operators. Inlining adds additional executor
+ startup overheads, but can improve performance during execution. It
+ is not meaningful to set this to less than
+ <varname>jit_above_cost</varname>. Setting this to
+ <literal>-1</literal> disables inlining. The default is
+ <literal>500000</literal>.
</para>
</listitem>
</varlistentry>
@@ -5639,13 +5643,13 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
</term>
<listitem>
<para>
- Sets the query cost above which JIT compilation applies expensive
- optimizations. Such optimization adds planning time, but can improve
- execution speed. It is not meaningful to set this to less
- than <varname>jit_above_cost</varname>, and it is unlikely to be
- beneficial to set it to more
- than <varname>jit_inline_above_cost</varname>.
- Setting this to <literal>-1</literal> disables expensive optimizations.
+ Sets the overall plan node cost above which JIT compilation applies expensive
+ optimizations. Such optimization adds executor startup overhead, but
+ can improve performance during execution. It is not meaningful to set
+ this to less than <varname>jit_above_cost</varname>, and it is
+ unlikely to be beneficial to set it to more than
+ <varname>jit_inline_above_cost</varname>.
+ Setting this to <literal>-1</literal> disables the optimization pass.
The default is <literal>500000</literal>.
</para>
</listitem>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index d2a2479822..fc36438df1 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1583,9 +1583,17 @@ ExplainNode(PlanState *planstate, List *ancestors,
{
if (es->format == EXPLAIN_FORMAT_TEXT)
{
- appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
- plan->startup_cost, plan->total_cost,
- plan->plan_rows, plan->plan_width);
+ /* only display the expected loops if it's above 1.0 */
+ if (plan->est_calls <= 1.0)
+ appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
+ plan->startup_cost, plan->total_cost,
+ plan->plan_rows, plan->plan_width);
+ else
+ appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d loops=%.0f)",
+ plan->startup_cost, plan->total_cost,
+ plan->plan_rows, plan->plan_width,
+ plan->est_calls);
+
}
else
{
@@ -1597,6 +1605,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
0, es);
ExplainPropertyInteger("Plan Width", NULL, plan->plan_width,
es);
+ ExplainPropertyFloat("Plan Calls", NULL, plan->est_calls, 0, es);
}
}
@@ -1719,6 +1728,21 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (es->verbose)
show_plan_tlist(planstate, ancestors, es);
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ /*
+ * If we did any jitting, indicate if we did any for this node or not.
+ * When format is TEXT, only do this when VERBOSE is enabled.
+ */
+ if (planstate->state->es_jit != NULL && es->verbose)
+ ExplainPropertyBool("JIT", plan->jit, es);
+ }
+ else
+ {
+ if (planstate->state->es_jit != NULL)
+ ExplainPropertyBool("JIT", plan->jit, es);
+ }
+
/* unique join */
switch (nodeTag(plan))
{
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index dda1c59b23..4854b16cfe 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -296,22 +296,8 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags)
if (estate->es_subplanstates &&
contain_subplans((Node *) exprs))
{
- int saved_jit_flags;
-
- /*
- * As these expressions are only used once, disable JIT for them.
- * This is worthwhile because it's common to insert significant
- * amounts of data via VALUES(). Note that this doesn't prevent
- * use of JIT *within* a subplan, since that's initialized
- * separately; this just affects the upper-level subexpressions.
- */
- saved_jit_flags = estate->es_jit_flags;
- estate->es_jit_flags = PGJIT_NONE;
-
scanstate->exprstatelists[i] = ExecInitExprList(exprs,
&scanstate->ss.ps);
-
- estate->es_jit_flags = saved_jit_flags;
}
i++;
}
diff --git a/src/backend/jit/README b/src/backend/jit/README
index 5427bdf215..a4aa33b79b 100644
--- a/src/backend/jit/README
+++ b/src/backend/jit/README
@@ -266,27 +266,32 @@ generation, and later compiling larger parts of queries.
When to JIT
===========
-Currently there are a number of GUCs that influence JITing:
-
-- jit_above_cost = -1, 0-DBL_MAX - all queries with a higher total cost
- get JITed, *without* optimization (expensive part), corresponding to
- -O0. This commonly already results in significant speedups if
- expression/deforming is a bottleneck (removing dynamic branches
- mostly).
-- jit_optimize_above_cost = -1, 0-DBL_MAX - all queries with a higher total cost
- get JITed, *with* optimization (expensive part).
-- jit_inline_above_cost = -1, 0-DBL_MAX - inlining is tried if query has
- higher cost.
-
-Whenever a query's total cost is above these limits, JITing is
-performed.
-
-Alternative costing models, e.g. by generating separate paths for
-parts of a query with lower cpu_* costs, are also a possibility, but
-it's doubtful the overhead of doing so is sufficient. Another
-alternative would be to count the number of times individual
+Currently, there are a number of GUCs that influence JITing. Each of these
+GUCs defines the cost that a given plan node must exceed to enable the given
+JIT feature. The costs here are calculated by multiplying the total_cost of
+the plan node by the estimated number of rescans the planner exepects the
+executor to perform on the given node. We refer to this cost as the "overall"
+cost in the text below:
+
+- jit_above_cost = -1, 0-DBL_MAX - all plan nodes which have a overall cost
+ higher than this value get JITed, *without* optimization (expensive part),
+ corresponding to -O0. This commonly already results in significant speedups
+ if expression/deforming is a bottleneck (removing dynamic branches mostly).
+- jit_optimize_above_cost = -1, 0-DBL_MAX - perform an optimization pass
+ during JIT compilation when any plan node that is eligible for JIT reaches
+ this cost.
+- jit_inline_above_cost = -1, 0-DBL_MAX - inlining is tried during JIT
+ compilation if any plan node has a higher overall cost than this value.
+
+It is important to remember that the optimize and inlining options are either
+enabled for all JIT enabled plan nodes or disabled. If a single node reaches
+the required threshold then all JIT enabled nodes will be JITed using the same
+compilation options.
+
+An alternative would be to count the number of times individual
expressions are estimated to be evaluated, and perform JITing of these
-individual expressions.
+individual expressions. This is probably more complexity than it would be
+worth.
The obvious seeming approach of JITing expressions individually after
a number of execution turns out not to work too well. Primarily
diff --git a/src/backend/jit/jit.c b/src/backend/jit/jit.c
index 18d168f1af..ade689bf22 100644
--- a/src/backend/jit/jit.c
+++ b/src/backend/jit/jit.c
@@ -172,6 +172,14 @@ jit_compile_expr(struct ExprState *state)
if (!(state->parent->state->es_jit_flags & PGJIT_EXPR))
return false;
+ /* don't jit if the plan node is missing */
+ if (state->parent->plan == NULL)
+ return false;
+
+ /* don't jit if it's not enabled for this plan node */
+ if (!state->parent->plan->jit)
+ return false;
+
/* this also takes !jit_enabled into account */
if (provider_init())
return provider.compile_expr(state);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 836f427ea8..e56db41047 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -128,6 +128,7 @@ CopyPlanFields(const Plan *from, Plan *newnode)
COPY_SCALAR_FIELD(parallel_aware);
COPY_SCALAR_FIELD(parallel_safe);
COPY_SCALAR_FIELD(async_capable);
+ COPY_SCALAR_FIELD(jit);
COPY_SCALAR_FIELD(plan_node_id);
COPY_NODE_FIELD(targetlist);
COPY_NODE_FIELD(qual);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6a02f81ad5..27360d587c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -341,6 +341,7 @@ _outPlanInfo(StringInfo str, const Plan *node)
WRITE_BOOL_FIELD(parallel_aware);
WRITE_BOOL_FIELD(parallel_safe);
WRITE_BOOL_FIELD(async_capable);
+ WRITE_BOOL_FIELD(jit);
WRITE_INT_FIELD(plan_node_id);
WRITE_NODE_FIELD(targetlist);
WRITE_NODE_FIELD(qual);
@@ -2117,6 +2118,7 @@ _outMemoizePath(StringInfo str, const MemoizePath *node)
WRITE_BOOL_FIELD(binary_mode);
WRITE_FLOAT_FIELD(calls, "%.0f");
WRITE_UINT_FIELD(est_entries);
+ WRITE_FLOAT_FIELD(est_hitratio, "%.6f");
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ddf76ac778..137ffbcdd1 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1847,6 +1847,7 @@ ReadCommonPlan(Plan *local_node)
READ_BOOL_FIELD(parallel_aware);
READ_BOOL_FIELD(parallel_safe);
READ_BOOL_FIELD(async_capable);
+ READ_BOOL_FIELD(jit);
READ_INT_FIELD(plan_node_id);
READ_NODE_FIELD(targetlist);
READ_NODE_FIELD(qual);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index b787c6f81a..c6cf109392 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -2814,6 +2814,12 @@ cost_memoize_rescan(PlannerInfo *root, MemoizePath *mpath,
/* Ensure we don't go negative */
hit_ratio = Max(hit_ratio, 0.0);
+ /*
+ * Since we've just gone to the bother of calculating the estimated hit
+ * ratio, let's store that in the MemoizePath for later use.
+ */
+ mpath->est_hitratio = hit_ratio;
+
/*
* Set the total_cost accounting for the expected cache hit ratio. We
* also add on a cpu_operator_cost to account for a cache lookup. This
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 7905bc4654..9c51103df1 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
#include "access/sysattr.h"
#include "catalog/pg_class.h"
#include "foreign/fdwapi.h"
+#include "jit/jit.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
#include "nodes/makefuncs.h"
@@ -73,95 +74,130 @@
static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path,
- int flags);
+ int flags, double est_calls);
static Plan *create_scan_plan(PlannerInfo *root, Path *best_path,
- int flags);
+ int flags, double est_calls);
+static void plan_consider_jit(PlannerInfo *root, Plan *plan);
static List *build_path_tlist(PlannerInfo *root, Path *path);
static bool use_physical_tlist(PlannerInfo *root, Path *path, int flags);
static List *get_gating_quals(PlannerInfo *root, List *quals);
static Plan *create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
- List *gating_quals);
-static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
+ List *gating_quals, double est_calls);
+static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path,
+ double est_calls);
static bool mark_async_capable_plan(Plan *plan, Path *path);
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path,
- int flags);
+ int flags, double est_calls);
static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
- int flags);
+ int flags, double est_calls);
static Result *create_group_result_plan(PlannerInfo *root,
- GroupResultPath *best_path);
-static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path);
+ GroupResultPath *best_path,
+ double est_calls);
+static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path,
+ double est_calls);
static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path,
- int flags);
+ int flags, double est_calls);
static Memoize *create_memoize_plan(PlannerInfo *root, MemoizePath *best_path,
- int flags);
+ int flags, double est_calls);
static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path,
- int flags);
-static Gather *create_gather_plan(PlannerInfo *root, GatherPath *best_path);
+ int flags, double est_calls);
+static Gather *create_gather_plan(PlannerInfo *root, GatherPath *best_path,
+ double est_calls);
static Plan *create_projection_plan(PlannerInfo *root,
ProjectionPath *best_path,
- int flags);
-static Plan *inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe);
-static Sort *create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags);
+ int flags, double est_calls);
+static Plan *inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe,
+ double est_calls);
+static Sort *create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags,
+ double est_calls);
static IncrementalSort *create_incrementalsort_plan(PlannerInfo *root,
- IncrementalSortPath *best_path, int flags);
-static Group *create_group_plan(PlannerInfo *root, GroupPath *best_path);
+ IncrementalSortPath *best_path, int flags,
+ double est_calls);
+static Group *create_group_plan(PlannerInfo *root, GroupPath *best_path,
+ double est_calls);
static Unique *create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path,
- int flags);
-static Agg *create_agg_plan(PlannerInfo *root, AggPath *best_path);
-static Plan *create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path);
-static Result *create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path);
-static WindowAgg *create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path);
+ int flags, double est_calls);
+static Agg *create_agg_plan(PlannerInfo *root, AggPath *best_path,
+ double est_calls);
+static Plan *create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path,
+ double est_calls);
+static Result *create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path,
+ double est_calls);
+static WindowAgg *create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path,
+ double est_calls);
static SetOp *create_setop_plan(PlannerInfo *root, SetOpPath *best_path,
- int flags);
-static RecursiveUnion *create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path);
+ int flags, double est_calls);
+static RecursiveUnion *create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path,
+ double est_calls);
static LockRows *create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
- int flags);
-static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path);
+ int flags, double est_calls);
+static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path,
+ double est_calls);
static Limit *create_limit_plan(PlannerInfo *root, LimitPath *best_path,
- int flags);
+ int flags, double est_calls);
static SeqScan *create_seqscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static SampleScan *create_samplescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static Scan *create_indexscan_plan(PlannerInfo *root, IndexPath *best_path,
- List *tlist, List *scan_clauses, bool indexonly);
+ List *tlist, List *scan_clauses, bool indexonly,
+ double est_calls);
static BitmapHeapScan *create_bitmap_scan_plan(PlannerInfo *root,
BitmapHeapPath *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
- List **qual, List **indexqual, List **indexECs);
+ List **qual, List **indexqual, List **indexECs,
+ double est_calls);
static void bitmap_subplan_mark_shared(Plan *plan);
static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static TidRangeScan *create_tidrangescan_plan(PlannerInfo *root,
TidRangePath *best_path,
List *tlist,
- List *scan_clauses);
+ List *scan_clauses,
+ double est_calls);
static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root,
SubqueryScanPath *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static TableFuncScan *create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static NamedTuplestoreScan *create_namedtuplestorescan_plan(PlannerInfo *root,
- Path *best_path, List *tlist, List *scan_clauses);
+ Path *best_path, List *tlist, List *scan_clauses,
+ double est_calls);
static Result *create_resultscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static CustomScan *create_customscan_plan(PlannerInfo *root,
CustomPath *best_path,
- List *tlist, List *scan_clauses);
-static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path);
-static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path);
-static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path);
+ List *tlist, List *scan_clauses,
+ double est_calls);
+static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
+ double est_calls);
+static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
+ double est_calls);
+static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
+ double est_calls);
static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
static void fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
@@ -269,11 +305,13 @@ static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
AttrNumber **p_sortColIdx,
Oid **p_sortOperators,
Oid **p_collations,
- bool **p_nullsFirst);
+ bool **p_nullsFirst,
+ double est_calls);
static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
- Relids relids);
+ Relids relids, double est_calls);
static IncrementalSort *make_incrementalsort_from_pathkeys(Plan *lefttree,
- List *pathkeys, Relids relids, int nPresortedCols);
+ List *pathkeys, Relids relids, int nPresortedCols,
+ double est_calls);
static Sort *make_sort_from_groupcols(List *groupcls,
AttrNumber *grpColIdx,
Plan *lefttree);
@@ -314,7 +352,8 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
List *rowMarks, OnConflictExpr *onconflict,
List *mergeActionList, int epqParam);
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
- GatherMergePath *best_path);
+ GatherMergePath *best_path,
+ double est_calls);
/*
@@ -330,10 +369,12 @@ static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
*
* best_path is the best access path
*
+ * est_calls is the number of expected times that we'll (re)scan this plan
+ *
* Returns a Plan tree.
*/
Plan *
-create_plan(PlannerInfo *root, Path *best_path)
+create_plan(PlannerInfo *root, Path *best_path, double est_calls)
{
Plan *plan;
@@ -345,7 +386,7 @@ create_plan(PlannerInfo *root, Path *best_path)
root->curOuterParams = NIL;
/* Recursively process the path tree, demanding the correct tlist result */
- plan = create_plan_recurse(root, best_path, CP_EXACT_TLIST);
+ plan = create_plan_recurse(root, best_path, CP_EXACT_TLIST, est_calls);
/*
* Make sure the topmost plan node's targetlist exposes the original
@@ -384,7 +425,7 @@ create_plan(PlannerInfo *root, Path *best_path)
* Recursive guts of create_plan().
*/
static Plan *
-create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
+create_plan_recurse(PlannerInfo *root, Path *best_path, int flags, double est_calls)
{
Plan *plan;
@@ -409,136 +450,147 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
case T_NamedTuplestoreScan:
case T_ForeignScan:
case T_CustomScan:
- plan = create_scan_plan(root, best_path, flags);
+ plan = create_scan_plan(root, best_path, flags, est_calls);
break;
case T_HashJoin:
case T_MergeJoin:
case T_NestLoop:
plan = create_join_plan(root,
- (JoinPath *) best_path);
+ (JoinPath *) best_path, est_calls);
break;
case T_Append:
plan = create_append_plan(root,
(AppendPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_MergeAppend:
plan = create_merge_append_plan(root,
(MergeAppendPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_Result:
if (IsA(best_path, ProjectionPath))
{
plan = create_projection_plan(root,
(ProjectionPath *) best_path,
- flags);
+ flags, est_calls);
}
else if (IsA(best_path, MinMaxAggPath))
{
plan = (Plan *) create_minmaxagg_plan(root,
- (MinMaxAggPath *) best_path);
+ (MinMaxAggPath *) best_path,
+ est_calls);
}
else if (IsA(best_path, GroupResultPath))
{
plan = (Plan *) create_group_result_plan(root,
- (GroupResultPath *) best_path);
+ (GroupResultPath *) best_path,
+ est_calls);
}
else
{
/* Simple RTE_RESULT base relation */
Assert(IsA(best_path, Path));
- plan = create_scan_plan(root, best_path, flags);
+ plan = create_scan_plan(root, best_path, flags, est_calls);
}
break;
case T_ProjectSet:
plan = (Plan *) create_project_set_plan(root,
- (ProjectSetPath *) best_path);
+ (ProjectSetPath *) best_path,
+ est_calls);
break;
case T_Material:
plan = (Plan *) create_material_plan(root,
(MaterialPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_Memoize:
plan = (Plan *) create_memoize_plan(root,
(MemoizePath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_Unique:
if (IsA(best_path, UpperUniquePath))
{
plan = (Plan *) create_upper_unique_plan(root,
(UpperUniquePath *) best_path,
- flags);
+ flags, est_calls);
}
else
{
Assert(IsA(best_path, UniquePath));
plan = create_unique_plan(root,
(UniquePath *) best_path,
- flags);
+ flags, est_calls);
}
break;
case T_Gather:
plan = (Plan *) create_gather_plan(root,
- (GatherPath *) best_path);
+ (GatherPath *) best_path,
+ est_calls);
break;
case T_Sort:
plan = (Plan *) create_sort_plan(root,
(SortPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_IncrementalSort:
plan = (Plan *) create_incrementalsort_plan(root,
(IncrementalSortPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_Group:
plan = (Plan *) create_group_plan(root,
- (GroupPath *) best_path);
+ (GroupPath *) best_path,
+ est_calls);
break;
case T_Agg:
if (IsA(best_path, GroupingSetsPath))
plan = create_groupingsets_plan(root,
- (GroupingSetsPath *) best_path);
+ (GroupingSetsPath *) best_path,
+ est_calls);
else
{
Assert(IsA(best_path, AggPath));
plan = (Plan *) create_agg_plan(root,
- (AggPath *) best_path);
+ (AggPath *) best_path,
+ est_calls);
}
break;
case T_WindowAgg:
plan = (Plan *) create_windowagg_plan(root,
- (WindowAggPath *) best_path);
+ (WindowAggPath *) best_path,
+ est_calls);
break;
case T_SetOp:
plan = (Plan *) create_setop_plan(root,
(SetOpPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_RecursiveUnion:
plan = (Plan *) create_recursiveunion_plan(root,
- (RecursiveUnionPath *) best_path);
+ (RecursiveUnionPath *) best_path,
+ est_calls);
break;
case T_LockRows:
plan = (Plan *) create_lockrows_plan(root,
(LockRowsPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_ModifyTable:
plan = (Plan *) create_modifytable_plan(root,
- (ModifyTablePath *) best_path);
+ (ModifyTablePath *) best_path,
+ est_calls);
break;
case T_Limit:
plan = (Plan *) create_limit_plan(root,
(LimitPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_GatherMerge:
plan = (Plan *) create_gather_merge_plan(root,
- (GatherMergePath *) best_path);
+ (GatherMergePath *) best_path,
+ est_calls);
break;
default:
elog(ERROR, "unrecognized node type: %d",
@@ -547,15 +599,75 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
break;
}
+ /* See about switching on JIT for this node */
+ plan_consider_jit(root, plan);
+
return plan;
}
+static void
+plan_consider_jit(PlannerInfo *root, Plan *plan)
+{
+ int jitflags = root->glob->jitFlags;
+
+ plan->jit = false;
+
+ /*
+ * For values scans, expressions are only used once, so ensure we don't
+ * enable JIT for them.
+ */
+ if (IsA(plan, ValuesScan))
+ return;
+
+ /* Determine which JIT options to enable for this plan node */
+ if (jit_enabled && jit_above_cost >= 0)
+ {
+ Cost total_cost;
+
+ /*
+ * Take into account the number of times that we expect to rescan a
+ * given plan node. For example, subplans being invoked under the
+ * inside of a Nested Loop may be rescanned many times. JITing these
+ * may be more worthwhile.
+ */
+ total_cost = plan->total_cost * plan->est_calls;
+
+ if (total_cost > jit_above_cost)
+ {
+ plan->jit = true;
+ jitflags |= PGJIT_PERFORM;
+
+ /*
+ * Decide how much effort should be put into generating better code.
+ */
+ if (jit_optimize_above_cost >= 0 &&
+ total_cost > jit_optimize_above_cost)
+ jitflags |= PGJIT_OPT3;
+ if (jit_inline_above_cost >= 0 &&
+ total_cost > jit_inline_above_cost)
+ jitflags |= PGJIT_INLINE;
+
+ /*
+ * Decide which operations should be JITed.
+ */
+ if (jit_expressions)
+ jitflags |= PGJIT_EXPR;
+ if (jit_tuple_deforming)
+ jitflags |= PGJIT_DEFORM;
+
+ /* Record the maximum flags used by any plan node */
+ root->glob->jitFlags |= jitflags;
+ }
+ }
+}
+
/*
* create_scan_plan
* Create a scan plan for the parent relation of 'best_path'.
*/
static Plan *
-create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
+create_scan_plan(PlannerInfo *root, Path *best_path, int flags,
+ double est_calls)
{
RelOptInfo *rel = best_path->parent;
List *scan_clauses;
@@ -660,14 +772,16 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
plan = (Plan *) create_seqscan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_SampleScan:
plan = (Plan *) create_samplescan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_IndexScan:
@@ -675,7 +789,8 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
(IndexPath *) best_path,
tlist,
scan_clauses,
- false);
+ false,
+ est_calls);
break;
case T_IndexOnlyScan:
@@ -683,98 +798,112 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
(IndexPath *) best_path,
tlist,
scan_clauses,
- true);
+ true,
+ est_calls);
break;
case T_BitmapHeapScan:
plan = (Plan *) create_bitmap_scan_plan(root,
(BitmapHeapPath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_TidScan:
plan = (Plan *) create_tidscan_plan(root,
(TidPath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_TidRangeScan:
plan = (Plan *) create_tidrangescan_plan(root,
(TidRangePath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_SubqueryScan:
plan = (Plan *) create_subqueryscan_plan(root,
(SubqueryScanPath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_FunctionScan:
plan = (Plan *) create_functionscan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_TableFuncScan:
plan = (Plan *) create_tablefuncscan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_ValuesScan:
plan = (Plan *) create_valuesscan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_CteScan:
plan = (Plan *) create_ctescan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_NamedTuplestoreScan:
plan = (Plan *) create_namedtuplestorescan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_Result:
plan = (Plan *) create_resultscan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_WorkTableScan:
plan = (Plan *) create_worktablescan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_ForeignScan:
plan = (Plan *) create_foreignscan_plan(root,
(ForeignPath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_CustomScan:
plan = (Plan *) create_customscan_plan(root,
(CustomPath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
default:
@@ -790,7 +919,8 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
* quals.
*/
if (gating_clauses)
- plan = create_gating_plan(root, best_path, plan, gating_clauses);
+ plan = create_gating_plan(root, best_path, plan, gating_clauses,
+ est_calls);
return plan;
}
@@ -1000,7 +1130,7 @@ get_gating_quals(PlannerInfo *root, List *quals)
*/
static Plan *
create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
- List *gating_quals)
+ List *gating_quals, double est_calls)
{
Plan *gplan;
Plan *splan;
@@ -1045,6 +1175,7 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
* gating qual being true.
*/
copy_plan_costsize(gplan, plan);
+ gplan->est_calls = clamp_row_est(est_calls);
/* Gating quals could be unsafe, so better use the Path's safety flag */
gplan->parallel_safe = path->parallel_safe;
@@ -1058,7 +1189,7 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
* inner and outer paths.
*/
static Plan *
-create_join_plan(PlannerInfo *root, JoinPath *best_path)
+create_join_plan(PlannerInfo *root, JoinPath *best_path, double est_calls)
{
Plan *plan;
List *gating_clauses;
@@ -1067,15 +1198,18 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
{
case T_MergeJoin:
plan = (Plan *) create_mergejoin_plan(root,
- (MergePath *) best_path);
+ (MergePath *) best_path,
+ est_calls);
break;
case T_HashJoin:
plan = (Plan *) create_hashjoin_plan(root,
- (HashPath *) best_path);
+ (HashPath *) best_path,
+ est_calls);
break;
case T_NestLoop:
plan = (Plan *) create_nestloop_plan(root,
- (NestPath *) best_path);
+ (NestPath *) best_path,
+ est_calls);
break;
default:
elog(ERROR, "unrecognized node type: %d",
@@ -1092,7 +1226,7 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
gating_clauses = get_gating_quals(root, best_path->joinrestrictinfo);
if (gating_clauses)
plan = create_gating_plan(root, (Path *) best_path, plan,
- gating_clauses);
+ gating_clauses, est_calls);
#ifdef NOT_USED
@@ -1107,6 +1241,8 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
get_actual_clauses(get_loc_restrictinfo(best_path))));
#endif
+ plan->est_calls = clamp_row_est(est_calls);
+
return plan;
}
@@ -1173,7 +1309,8 @@ mark_async_capable_plan(Plan *plan, Path *path)
* Returns a Plan node.
*/
static Plan *
-create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
+create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags,
+ double est_calls)
{
Append *plan;
List *tlist = build_path_tlist(root, &best_path->path);
@@ -1250,7 +1387,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
&nodeSortColIdx,
&nodeSortOperators,
&nodeCollations,
- &nodeNullsFirst);
+ &nodeNullsFirst,
+ est_calls);
tlist_was_changed = (orig_tlist_length != list_length(plan->plan.targetlist));
}
@@ -1266,7 +1404,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
Plan *subplan;
/* Must insist that all children return the same tlist */
- subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
+ subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST, est_calls);
/*
* For ordered Appends, we must insert a Sort node if subplan isn't
@@ -1294,7 +1432,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ est_calls);
/*
* Check that we got the same sort key information. We just
@@ -1370,6 +1509,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
plan->part_prune_info = partpruneinfo;
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
/*
* If prepare_sort_from_pathkeys added sort columns, but we were told to
@@ -1381,7 +1521,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
tlist = list_truncate(list_copy(plan->plan.targetlist),
orig_tlist_length);
return inject_projection_plan((Plan *) plan, tlist,
- plan->plan.parallel_safe);
+ plan->plan.parallel_safe, est_calls);
}
else
return (Plan *) plan;
@@ -1396,7 +1536,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
*/
static Plan *
create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
- int flags)
+ int flags, double est_calls)
{
MergeAppend *node = makeNode(MergeAppend);
Plan *plan = &node->plan;
@@ -1416,6 +1556,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
* child plans, to make cross-checking the sort info easier.
*/
copy_generic_path_info(plan, (Path *) best_path);
+ plan->est_calls = clamp_row_est(est_calls);
plan->targetlist = tlist;
plan->qual = NIL;
plan->lefttree = NULL;
@@ -1436,7 +1577,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
&node->sortColIdx,
&node->sortOperators,
&node->collations,
- &node->nullsFirst);
+ &node->nullsFirst,
+ est_calls);
tlist_was_changed = (orig_tlist_length != list_length(plan->targetlist));
/*
@@ -1456,7 +1598,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
/* Build the child plan */
/* Must insist that all children return the same tlist */
- subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
+ subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST, est_calls);
/* Compute sort column info, and adjust subplan's tlist as needed */
subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
@@ -1467,7 +1609,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ est_calls);
/*
* Check that we got the same sort key information. We just Assert
@@ -1539,7 +1682,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
if (tlist_was_changed && (flags & (CP_EXACT_TLIST | CP_SMALL_TLIST)))
{
tlist = list_truncate(list_copy(plan->targetlist), orig_tlist_length);
- return inject_projection_plan(plan, tlist, plan->parallel_safe);
+ return inject_projection_plan(plan, tlist, plan->parallel_safe,
+ est_calls);
}
else
return plan;
@@ -1553,7 +1697,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
* Returns a Plan node.
*/
static Result *
-create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
+create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path,
+ double est_calls)
{
Result *plan;
List *tlist;
@@ -1567,6 +1712,7 @@ create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
plan = make_result(tlist, (Node *) quals, NULL);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -1578,20 +1724,22 @@ create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
* Returns a Plan node.
*/
static ProjectSet *
-create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path)
+create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path,
+ double est_calls)
{
ProjectSet *plan;
Plan *subplan;
List *tlist;
/* Since we intend to project, we don't need to constrain child tlist */
- subplan = create_plan_recurse(root, best_path->subpath, 0);
+ subplan = create_plan_recurse(root, best_path->subpath, 0, est_calls);
tlist = build_path_tlist(root, &best_path->path);
plan = make_project_set(tlist, subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -1604,7 +1752,8 @@ create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path)
* Returns a Plan node.
*/
static Material *
-create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags)
+create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags,
+ double est_calls)
{
Material *plan;
Plan *subplan;
@@ -1612,14 +1761,21 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags)
/*
* We don't want any excess columns in the materialized tuples, so request
* a smaller tlist. Otherwise, since Material doesn't project, tlist
- * requirements pass through.
+ * requirements pass through. Here we also don't propagate the est_calls
+ * to the subplan. We assume that the Material node will only call its
+ * subplan once and then returned the cached version on each subsequent
+ * execution. This might not be true when a parameter change causes the
+ * Material node to have to rescan, but that's hard to estimate here and
+ * the current usages of est_calls does not seem important enough to
+ * warrant expending too much effort trying to calculate this.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- flags | CP_SMALL_TLIST);
+ flags | CP_SMALL_TLIST, 1.0);
plan = make_material(subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -1632,7 +1788,8 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags)
* Returns a Plan node.
*/
static Memoize *
-create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags)
+create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags,
+ double est_calls)
{
Memoize *plan;
Bitmapset *keyparamids;
@@ -1645,8 +1802,13 @@ create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags)
int nkeys;
int i;
+ /*
+ * est_calls must take into account the expected hit ratio of the cache.
+ * We'll only be calling the subplan when it's a cache miss.
+ */
subplan = create_plan_recurse(root, best_path->subpath,
- flags | CP_SMALL_TLIST);
+ flags | CP_SMALL_TLIST,
+ est_calls * (1.0 - best_path->est_hitratio));
param_exprs = (List *) replace_nestloop_params(root, (Node *)
best_path->param_exprs);
@@ -1674,6 +1836,7 @@ create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags)
best_path->est_entries, keyparamids);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -1686,7 +1849,8 @@ create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags)
* Returns a Plan node.
*/
static Plan *
-create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
+create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags,
+ double est_calls)
{
Plan *plan;
Plan *subplan;
@@ -1702,7 +1866,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
ListCell *l;
/* Unique doesn't project, so tlist requirements pass through */
- subplan = create_plan_recurse(root, best_path->subpath, flags);
+ subplan = create_plan_recurse(root, best_path->subpath, flags, est_calls);
/* Done if we don't need to do any actual unique-ifying */
if (best_path->umethod == UNIQUE_PATH_NOOP)
@@ -1753,7 +1917,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
/* Use change_plan_targetlist in case we need to insert a Result node */
if (newitems || best_path->umethod == UNIQUE_PATH_SORT)
subplan = change_plan_targetlist(subplan, newtlist,
- best_path->path.parallel_safe);
+ best_path->path.parallel_safe,
+ est_calls);
/*
* Build control information showing which subplan output columns are to
@@ -1874,6 +2039,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
/* Copy cost data from Path to Plan */
copy_generic_path_info(plan, &best_path->path);
+ plan->est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -1885,7 +2051,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
* for its subpaths.
*/
static Gather *
-create_gather_plan(PlannerInfo *root, GatherPath *best_path)
+create_gather_plan(PlannerInfo *root, GatherPath *best_path, double est_calls)
{
Gather *gather_plan;
Plan *subplan;
@@ -1897,7 +2063,8 @@ create_gather_plan(PlannerInfo *root, GatherPath *best_path)
* can't travel through a tuple queue because it uses MinimalTuple
* representation).
*/
- subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST);
+ subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST,
+ est_calls);
tlist = build_path_tlist(root, &best_path->path);
@@ -1909,6 +2076,7 @@ create_gather_plan(PlannerInfo *root, GatherPath *best_path)
subplan);
copy_generic_path_info(&gather_plan->plan, &best_path->path);
+ gather_plan->plan.est_calls = clamp_row_est(est_calls);
/* use parallel mode for parallel plans. */
root->glob->parallelModeNeeded = true;
@@ -1923,7 +2091,8 @@ create_gather_plan(PlannerInfo *root, GatherPath *best_path)
* plans for its subpaths.
*/
static GatherMerge *
-create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
+create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path,
+ double est_calls)
{
GatherMerge *gm_plan;
Plan *subplan;
@@ -1931,13 +2100,15 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
List *tlist = build_path_tlist(root, &best_path->path);
/* As with Gather, project away columns in the workers. */
- subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST);
+ subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST,
+ est_calls);
/* Create a shell for a GatherMerge plan. */
gm_plan = makeNode(GatherMerge);
gm_plan->plan.targetlist = tlist;
gm_plan->num_workers = best_path->num_workers;
copy_generic_path_info(&gm_plan->plan, &best_path->path);
+ gm_plan->plan.est_calls = clamp_row_est(est_calls);
/* Assign the rescan Param. */
gm_plan->rescan_param = assign_special_exec_param(root);
@@ -1954,7 +2125,8 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
&gm_plan->sortColIdx,
&gm_plan->sortOperators,
&gm_plan->collations,
- &gm_plan->nullsFirst);
+ &gm_plan->nullsFirst,
+ est_calls);
/*
@@ -1984,7 +2156,8 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
* but sometimes we can just let the subplan do the work.
*/
static Plan *
-create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
+create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags,
+ double est_calls)
{
Plan *plan;
Plan *subplan;
@@ -2011,7 +2184,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
* actually need to project. However, we may still need to ensure
* proper sortgroupref labels, if the caller cares about those.
*/
- subplan = create_plan_recurse(root, best_path->subpath, 0);
+ subplan = create_plan_recurse(root, best_path->subpath, 0, est_calls);
tlist = subplan->targetlist;
if (flags & CP_LABEL_TLIST)
apply_pathtarget_labeling_to_tlist(tlist,
@@ -2026,7 +2199,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
* produces.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- CP_IGNORE_TLIST);
+ CP_IGNORE_TLIST, est_calls);
Assert(is_projection_capable_plan(subplan));
tlist = build_path_tlist(root, &best_path->path);
}
@@ -2036,7 +2209,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
* It looks like we need a result node, unless by good fortune the
* requested tlist is exactly the one the child wants to produce.
*/
- subplan = create_plan_recurse(root, best_path->subpath, 0);
+ subplan = create_plan_recurse(root, best_path->subpath, 0, est_calls);
tlist = build_path_tlist(root, &best_path->path);
needs_result_node = !tlist_same_exprs(tlist, subplan->targetlist);
}
@@ -2069,6 +2242,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
plan = (Plan *) make_result(tlist, NULL, subplan);
copy_generic_path_info(plan, (Path *) best_path);
+ plan->est_calls = clamp_row_est(est_calls);
}
return plan;
@@ -2086,7 +2260,8 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
* to apply (since the tlist might be unsafe even if the child plan is safe).
*/
static Plan *
-inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe)
+inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe,
+ double est_calls)
{
Plan *plan;
@@ -2100,6 +2275,7 @@ inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe)
* consistent not more so. Hence, just copy the subplan's cost.
*/
copy_plan_costsize(plan, subplan);
+ plan->est_calls = clamp_row_est(est_calls);
plan->parallel_safe = parallel_safe;
return plan;
@@ -2118,7 +2294,8 @@ inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe)
* flag of the FDW's own Path node.
*/
Plan *
-change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe)
+change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe,
+ double est_calls)
{
/*
* If the top plan node can't do projections and its existing target list
@@ -2129,7 +2306,7 @@ change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe)
!tlist_same_exprs(tlist, subplan->targetlist))
subplan = inject_projection_plan(subplan, tlist,
subplan->parallel_safe &&
- tlist_parallel_safe);
+ tlist_parallel_safe, est_calls);
else
{
/* Else we can just replace the plan node's tlist */
@@ -2146,7 +2323,8 @@ change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe)
* for its subpaths.
*/
static Sort *
-create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
+create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags,
+ double est_calls)
{
Sort *plan;
Plan *subplan;
@@ -2157,7 +2335,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
* requirements pass through.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- flags | CP_SMALL_TLIST);
+ flags | CP_SMALL_TLIST, est_calls);
/*
* make_sort_from_pathkeys indirectly calls find_ec_member_matching_expr,
@@ -2167,9 +2345,11 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
*/
plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys,
IS_OTHER_REL(best_path->subpath->parent) ?
- best_path->path.parent->relids : NULL);
+ best_path->path.parent->relids : NULL,
+ est_calls);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2181,21 +2361,22 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
*/
static IncrementalSort *
create_incrementalsort_plan(PlannerInfo *root, IncrementalSortPath *best_path,
- int flags)
+ int flags, double est_calls)
{
IncrementalSort *plan;
Plan *subplan;
/* See comments in create_sort_plan() above */
subplan = create_plan_recurse(root, best_path->spath.subpath,
- flags | CP_SMALL_TLIST);
+ flags | CP_SMALL_TLIST, est_calls);
plan = make_incrementalsort_from_pathkeys(subplan,
best_path->spath.path.pathkeys,
IS_OTHER_REL(best_path->spath.subpath->parent) ?
best_path->spath.path.parent->relids : NULL,
- best_path->nPresortedCols);
+ best_path->nPresortedCols, est_calls);
copy_generic_path_info(&plan->sort.plan, (Path *) best_path);
+ plan->sort.plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2207,7 +2388,7 @@ create_incrementalsort_plan(PlannerInfo *root, IncrementalSortPath *best_path,
* for its subpaths.
*/
static Group *
-create_group_plan(PlannerInfo *root, GroupPath *best_path)
+create_group_plan(PlannerInfo *root, GroupPath *best_path, double est_calls)
{
Group *plan;
Plan *subplan;
@@ -2218,7 +2399,8 @@ create_group_plan(PlannerInfo *root, GroupPath *best_path)
* Group can project, so no need to be terribly picky about child tlist,
* but we do need grouping columns to be available
*/
- subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
+ subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST,
+ est_calls);
tlist = build_path_tlist(root, &best_path->path);
@@ -2235,6 +2417,7 @@ create_group_plan(PlannerInfo *root, GroupPath *best_path)
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2246,7 +2429,8 @@ create_group_plan(PlannerInfo *root, GroupPath *best_path)
* for its subpaths.
*/
static Unique *
-create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flags)
+create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flags,
+ double est_calls)
{
Unique *plan;
Plan *subplan;
@@ -2256,13 +2440,14 @@ create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flag
* need grouping columns to be labeled.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- flags | CP_LABEL_TLIST);
+ flags | CP_LABEL_TLIST, est_calls);
plan = make_unique_from_pathkeys(subplan,
best_path->path.pathkeys,
best_path->numkeys);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2274,7 +2459,7 @@ create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flag
* for its subpaths.
*/
static Agg *
-create_agg_plan(PlannerInfo *root, AggPath *best_path)
+create_agg_plan(PlannerInfo *root, AggPath *best_path, double est_calls)
{
Agg *plan;
Plan *subplan;
@@ -2285,7 +2470,8 @@ create_agg_plan(PlannerInfo *root, AggPath *best_path)
* Agg can project, so no need to be terribly picky about child tlist, but
* we do need grouping columns to be available
*/
- subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
+ subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST,
+ est_calls);
tlist = build_path_tlist(root, &best_path->path);
@@ -2307,6 +2493,7 @@ create_agg_plan(PlannerInfo *root, AggPath *best_path)
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2358,7 +2545,8 @@ remap_groupColIdx(PlannerInfo *root, List *groupClause)
* Returns a Plan node.
*/
static Plan *
-create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
+create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path,
+ double est_calls)
{
Agg *plan;
Plan *subplan;
@@ -2376,7 +2564,8 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
* Agg can project, so no need to be terribly picky about child tlist, but
* we do need grouping columns to be available
*/
- subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
+ subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST,
+ est_calls);
/*
* Compute the mapping from tleSortGroupRef to column index in the child's
@@ -2471,7 +2660,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
sort_plan->targetlist = NIL;
sort_plan->lefttree = NULL;
}
-
+ /* XXX do we need to record est_calls here? */
chain = lappend(chain, agg_plan);
}
}
@@ -2504,6 +2693,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
/* Copy cost data from Path to Plan */
copy_generic_path_info(&plan->plan, &best_path->path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
}
return (Plan *) plan;
@@ -2516,7 +2706,8 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
* for its subpaths.
*/
static Result *
-create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
+create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path,
+ double est_calls)
{
Result *plan;
List *tlist;
@@ -2536,7 +2727,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
* Since we are entering a different planner context (subroot),
* recurse to create_plan not create_plan_recurse.
*/
- plan = create_plan(subroot, mminfo->path);
+ plan = create_plan(subroot, mminfo->path, est_calls);
plan = (Plan *) make_limit(plan,
subparse->limitOffset,
@@ -2562,6 +2753,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
plan = make_result(tlist, (Node *) best_path->quals, NULL);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
/*
* During setrefs.c, we'll need to replace references to the Agg nodes
@@ -2582,7 +2774,8 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
* for its subpaths.
*/
static WindowAgg *
-create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
+create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path,
+ double est_calls)
{
WindowAgg *plan;
WindowClause *wc = best_path->winclause;
@@ -2607,7 +2800,7 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
* course need grouping columns to be available.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- CP_LABEL_TLIST | CP_SMALL_TLIST);
+ CP_LABEL_TLIST | CP_SMALL_TLIST, est_calls);
tlist = build_path_tlist(root, &best_path->path);
@@ -2679,6 +2872,7 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2690,7 +2884,8 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
* for its subpaths.
*/
static SetOp *
-create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags)
+create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags,
+ double est_calls)
{
SetOp *plan;
Plan *subplan;
@@ -2701,7 +2896,7 @@ create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags)
* need grouping columns to be labeled.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- flags | CP_LABEL_TLIST);
+ flags | CP_LABEL_TLIST, est_calls);
/* Convert numGroups to long int --- but 'ware overflow! */
numGroups = (long) Min(best_path->numGroups, (double) LONG_MAX);
@@ -2715,6 +2910,7 @@ create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags)
numGroups);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2726,7 +2922,8 @@ create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags)
* for its subpaths.
*/
static RecursiveUnion *
-create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path)
+create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path,
+ double est_calls)
{
RecursiveUnion *plan;
Plan *leftplan;
@@ -2735,8 +2932,10 @@ create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path)
long numGroups;
/* Need both children to produce same tlist, so force it */
- leftplan = create_plan_recurse(root, best_path->leftpath, CP_EXACT_TLIST);
- rightplan = create_plan_recurse(root, best_path->rightpath, CP_EXACT_TLIST);
+ leftplan = create_plan_recurse(root, best_path->leftpath, CP_EXACT_TLIST,
+ est_calls);
+ rightplan = create_plan_recurse(root, best_path->rightpath, CP_EXACT_TLIST,
+ est_calls);
tlist = build_path_tlist(root, &best_path->path);
@@ -2751,6 +2950,7 @@ create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path)
numGroups);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2763,17 +2963,18 @@ create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path)
*/
static LockRows *
create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
- int flags)
+ int flags, double est_calls)
{
LockRows *plan;
Plan *subplan;
/* LockRows doesn't project, so tlist requirements pass through */
- subplan = create_plan_recurse(root, best_path->subpath, flags);
+ subplan = create_plan_recurse(root, best_path->subpath, flags, est_calls);
plan = make_lockrows(subplan, best_path->rowMarks, best_path->epqParam);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2785,14 +2986,15 @@ create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
* Returns a Plan node.
*/
static ModifyTable *
-create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
+create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path,
+ double est_calls)
{
ModifyTable *plan;
Path *subpath = best_path->subpath;
Plan *subplan;
/* Subplan must produce exactly the specified tlist */
- subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
+ subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST, est_calls);
/* Transfer resname/resjunk labeling, too, to keep executor happy */
apply_tlist_labeling(subplan->targetlist, root->processed_tlist);
@@ -2814,6 +3016,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
best_path->epqParam);
copy_generic_path_info(&plan->plan, &best_path->path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2825,7 +3028,8 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
* for its subpaths.
*/
static Limit *
-create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
+create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags,
+ double est_calls)
{
Limit *plan;
Plan *subplan;
@@ -2835,7 +3039,7 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
Oid *uniqCollations = NULL;
/* Limit doesn't project, so tlist requirements pass through */
- subplan = create_plan_recurse(root, best_path->subpath, flags);
+ subplan = create_plan_recurse(root, best_path->subpath, flags, est_calls);
/* Extract information necessary for comparing rows for WITH TIES. */
if (best_path->limitOption == LIMIT_OPTION_WITH_TIES)
@@ -2868,6 +3072,7 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
numUniqkeys, uniqColIdx, uniqOperators, uniqCollations);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2887,7 +3092,7 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
*/
static SeqScan *
create_seqscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
SeqScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -2914,6 +3119,7 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
scan_relid);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -2925,7 +3131,7 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
*/
static SampleScan *
create_samplescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
SampleScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -2960,6 +3166,7 @@ create_samplescan_plan(PlannerInfo *root, Path *best_path,
tsc);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -2979,7 +3186,7 @@ create_indexscan_plan(PlannerInfo *root,
IndexPath *best_path,
List *tlist,
List *scan_clauses,
- bool indexonly)
+ bool indexonly, double est_calls)
{
Scan *scan_plan;
List *indexclauses = best_path->indexclauses;
@@ -3158,6 +3365,7 @@ create_indexscan_plan(PlannerInfo *root,
best_path->indexscandir);
copy_generic_path_info(&scan_plan->plan, &best_path->path);
+ scan_plan->plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3171,7 +3379,7 @@ static BitmapHeapScan *
create_bitmap_scan_plan(PlannerInfo *root,
BitmapHeapPath *best_path,
List *tlist,
- List *scan_clauses)
+ List *scan_clauses, double est_calls)
{
Index baserelid = best_path->path.parent->relid;
Plan *bitmapqualplan;
@@ -3189,7 +3397,7 @@ create_bitmap_scan_plan(PlannerInfo *root,
/* Process the bitmapqual tree into a Plan tree and qual lists */
bitmapqualplan = create_bitmap_subplan(root, best_path->bitmapqual,
&bitmapqualorig, &indexquals,
- &indexECs);
+ &indexECs, est_calls);
if (best_path->path.parallel_aware)
bitmap_subplan_mark_shared(bitmapqualplan);
@@ -3273,6 +3481,7 @@ create_bitmap_scan_plan(PlannerInfo *root,
baserelid);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3299,7 +3508,8 @@ create_bitmap_scan_plan(PlannerInfo *root,
*/
static Plan *
create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
- List **qual, List **indexqual, List **indexECs)
+ List **qual, List **indexqual, List **indexECs,
+ double est_calls)
{
Plan *plan;
@@ -3328,7 +3538,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
&subqual, &subindexqual,
- &subindexEC);
+ &subindexEC, est_calls);
subplans = lappend(subplans, subplan);
subquals = list_concat_unique(subquals, subqual);
subindexquals = list_concat_unique(subindexquals, subindexqual);
@@ -3375,7 +3585,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
&subqual, &subindexqual,
- &subindexEC);
+ &subindexEC, est_calls);
subplans = lappend(subplans, subplan);
if (subqual == NIL)
const_true_subqual = true;
@@ -3440,7 +3650,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
/* Use the regular indexscan plan build machinery... */
iscan = castNode(IndexScan,
create_indexscan_plan(root, ipath,
- NIL, NIL, false));
+ NIL, NIL, false, est_calls));
/* then convert to a bitmap indexscan */
plan = (Plan *) make_bitmap_indexscan(iscan->scan.scanrelid,
iscan->indexid,
@@ -3507,7 +3717,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
*/
static TidScan *
create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
TidScan *scan_plan;
Index scan_relid = best_path->path.parent->relid;
@@ -3593,6 +3803,7 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
tidquals);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3604,7 +3815,7 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
*/
static TidRangeScan *
create_tidrangescan_plan(PlannerInfo *root, TidRangePath *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
TidRangeScan *scan_plan;
Index scan_relid = best_path->path.parent->relid;
@@ -3658,6 +3869,7 @@ create_tidrangescan_plan(PlannerInfo *root, TidRangePath *best_path,
tidrangequals);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3669,7 +3881,7 @@ create_tidrangescan_plan(PlannerInfo *root, TidRangePath *best_path,
*/
static SubqueryScan *
create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
SubqueryScan *scan_plan;
RelOptInfo *rel = best_path->path.parent;
@@ -3685,7 +3897,7 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
* a different planner context (subroot), recurse to create_plan not
* create_plan_recurse.
*/
- subplan = create_plan(rel->subroot, best_path->subpath);
+ subplan = create_plan(rel->subroot, best_path->subpath, est_calls);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
@@ -3708,6 +3920,7 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
subplan);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3719,7 +3932,7 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
*/
static FunctionScan *
create_functionscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
FunctionScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -3751,6 +3964,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
functions, rte->funcordinality);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3762,7 +3976,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
*/
static TableFuncScan *
create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
TableFuncScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -3794,6 +4008,7 @@ create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
tablefunc);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3805,7 +4020,7 @@ create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
*/
static ValuesScan *
create_valuesscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
ValuesScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -3838,6 +4053,7 @@ create_valuesscan_plan(PlannerInfo *root, Path *best_path,
values_lists);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3849,7 +4065,7 @@ create_valuesscan_plan(PlannerInfo *root, Path *best_path,
*/
static CteScan *
create_ctescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
CteScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -3932,6 +4148,7 @@ create_ctescan_plan(PlannerInfo *root, Path *best_path,
plan_id, cte_param_id);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3944,7 +4161,8 @@ create_ctescan_plan(PlannerInfo *root, Path *best_path,
*/
static NamedTuplestoreScan *
create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses,
+ double est_calls)
{
NamedTuplestoreScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -3971,6 +4189,7 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
rte->enrname);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3983,7 +4202,7 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
*/
static Result *
create_resultscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
Result *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -4009,6 +4228,7 @@ create_resultscan_plan(PlannerInfo *root, Path *best_path,
scan_plan = make_result(tlist, (Node *) scan_clauses, NULL);
copy_generic_path_info(&scan_plan->plan, best_path);
+ scan_plan->plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -4020,7 +4240,7 @@ create_resultscan_plan(PlannerInfo *root, Path *best_path,
*/
static WorkTableScan *
create_worktablescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
WorkTableScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -4069,6 +4289,7 @@ create_worktablescan_plan(PlannerInfo *root, Path *best_path,
cteroot->wt_param_id);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -4080,7 +4301,7 @@ create_worktablescan_plan(PlannerInfo *root, Path *best_path,
*/
static ForeignScan *
create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
ForeignScan *scan_plan;
RelOptInfo *rel = best_path->path.parent;
@@ -4093,7 +4314,7 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
/* transform the child path if any */
if (best_path->fdw_outerpath)
outer_plan = create_plan_recurse(root, best_path->fdw_outerpath,
- CP_EXACT_TLIST);
+ CP_EXACT_TLIST, est_calls);
/*
* If we're scanning a base relation, fetch its OID. (Irrelevant if
@@ -4125,10 +4346,11 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rel_oid,
best_path,
tlist, scan_clauses,
- outer_plan);
+ outer_plan, est_calls);
/* Copy cost data from Path to Plan; no need to make FDW do this */
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
/* Copy foreign server OID; likewise, no need to make FDW do this */
scan_plan->fs_server = rel->serverid;
@@ -4224,7 +4446,7 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
*/
static CustomScan *
create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
CustomScan *cplan;
RelOptInfo *rel = best_path->path.parent;
@@ -4235,7 +4457,7 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
foreach(lc, best_path->custom_paths)
{
Plan *plan = create_plan_recurse(root, (Path *) lfirst(lc),
- CP_EXACT_TLIST);
+ CP_EXACT_TLIST, est_calls);
custom_plans = lappend(custom_plans, plan);
}
@@ -4263,6 +4485,7 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
* do this
*/
copy_generic_path_info(&cplan->scan.plan, &best_path->path);
+ cplan->scan.plan.est_calls = clamp_row_est(est_calls);
/* Likewise, copy the relids that are represented by this custom scan */
cplan->custom_relids = best_path->path.parent->relids;
@@ -4295,7 +4518,7 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
static NestLoop *
create_nestloop_plan(PlannerInfo *root,
- NestPath *best_path)
+ NestPath *best_path, double est_calls)
{
NestLoop *join_plan;
Plan *outer_plan;
@@ -4309,13 +4532,15 @@ create_nestloop_plan(PlannerInfo *root,
Relids saveOuterRels = root->curOuterRels;
/* NestLoop can project, so no need to be picky about child tlists */
- outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath, 0);
+ outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath, 0,
+ est_calls);
/* For a nestloop, include outer relids in curOuterRels for inner side */
root->curOuterRels = bms_union(root->curOuterRels,
best_path->jpath.outerjoinpath->parent->relids);
- inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath, 0);
+ inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath, 0,
+ est_calls * outer_plan->plan_rows);
/* Restore curOuterRels */
bms_free(root->curOuterRels);
@@ -4365,13 +4590,14 @@ create_nestloop_plan(PlannerInfo *root,
best_path->jpath.inner_unique);
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
+ join_plan->join.plan.est_calls = clamp_row_est(est_calls);
return join_plan;
}
static MergeJoin *
create_mergejoin_plan(PlannerInfo *root,
- MergePath *best_path)
+ MergePath *best_path, double est_calls)
{
MergeJoin *join_plan;
Plan *outer_plan;
@@ -4403,10 +4629,12 @@ create_mergejoin_plan(PlannerInfo *root,
* necessary.
*/
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
- (best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0);
+ (best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0,
+ est_calls);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
- (best_path->innersortkeys != NIL) ? CP_SMALL_TLIST : 0);
+ (best_path->innersortkeys != NIL) ? CP_SMALL_TLIST : 0,
+ est_calls);
/* Sort join qual clauses into best execution order */
/* NB: do NOT reorder the mergeclauses */
@@ -4462,7 +4690,7 @@ create_mergejoin_plan(PlannerInfo *root,
Relids outer_relids = outer_path->parent->relids;
Sort *sort = make_sort_from_pathkeys(outer_plan,
best_path->outersortkeys,
- outer_relids);
+ outer_relids, est_calls);
label_sort_with_costsize(root, sort, -1.0);
outer_plan = (Plan *) sort;
@@ -4476,7 +4704,7 @@ create_mergejoin_plan(PlannerInfo *root,
Relids inner_relids = inner_path->parent->relids;
Sort *sort = make_sort_from_pathkeys(inner_plan,
best_path->innersortkeys,
- inner_relids);
+ inner_relids, est_calls);
label_sort_with_costsize(root, sort, -1.0);
inner_plan = (Plan *) sort;
@@ -4499,6 +4727,7 @@ create_mergejoin_plan(PlannerInfo *root,
* sync with final_cost_mergejoin.)
*/
copy_plan_costsize(matplan, inner_plan);
+ inner_plan->est_calls = clamp_row_est(est_calls);
matplan->total_cost += cpu_operator_cost * matplan->plan_rows;
inner_plan = matplan;
@@ -4672,13 +4901,14 @@ create_mergejoin_plan(PlannerInfo *root,
/* Costs of sort and material steps are included in path cost already */
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
+ join_plan->join.plan.est_calls = clamp_row_est(est_calls);
return join_plan;
}
static HashJoin *
create_hashjoin_plan(PlannerInfo *root,
- HashPath *best_path)
+ HashPath *best_path, double est_calls)
{
HashJoin *join_plan;
Hash *hash_plan;
@@ -4705,10 +4935,11 @@ create_hashjoin_plan(PlannerInfo *root,
* that we don't put extra data in the outer batch files.
*/
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
- (best_path->num_batches > 1) ? CP_SMALL_TLIST : 0);
+ (best_path->num_batches > 1) ? CP_SMALL_TLIST : 0,
+ est_calls);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
- CP_SMALL_TLIST);
+ CP_SMALL_TLIST, est_calls);
/* Sort join qual clauses into best execution order */
joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo);
@@ -4845,6 +5076,7 @@ create_hashjoin_plan(PlannerInfo *root,
best_path->jpath.inner_unique);
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
+ join_plan->join.plan.est_calls = clamp_row_est(est_calls);
return join_plan;
}
@@ -6106,7 +6338,8 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
AttrNumber **p_sortColIdx,
Oid **p_sortOperators,
Oid **p_collations,
- bool **p_nullsFirst)
+ bool **p_nullsFirst,
+ double est_calls)
{
List *tlist = lefttree->targetlist;
ListCell *i;
@@ -6226,7 +6459,8 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
/* copy needed so we don't modify input's tlist below */
tlist = copyObject(tlist);
lefttree = inject_projection_plan(lefttree, tlist,
- lefttree->parallel_safe);
+ lefttree->parallel_safe,
+ est_calls);
}
/* Don't bother testing is_projection_capable_plan again */
@@ -6283,7 +6517,8 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
* 'relids' is the set of relations required by prepare_sort_from_pathkeys()
*/
static Sort *
-make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
+make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids,
+ double est_calls)
{
int numsortkeys;
AttrNumber *sortColIdx;
@@ -6300,7 +6535,8 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ est_calls);
/* Now build the Sort node */
return make_sort(lefttree, numsortkeys,
@@ -6319,7 +6555,8 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
*/
static IncrementalSort *
make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
- Relids relids, int nPresortedCols)
+ Relids relids, int nPresortedCols,
+ double est_calls)
{
int numsortkeys;
AttrNumber *sortColIdx;
@@ -6336,7 +6573,8 @@ make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ est_calls);
/* Now build the Sort node */
return make_incrementalsort(lefttree, numsortkeys, nPresortedCols,
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9a4accb4d9..7b2552c0e4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,6 +314,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
glob->lastPHId = 0;
glob->lastRowMarkId = 0;
glob->lastPlanNodeId = 0;
+ glob->jitFlags = PGJIT_NONE;
glob->transientPlan = false;
glob->dependsOnRole = false;
@@ -410,7 +411,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
- top_plan = create_plan(root, best_path);
+ top_plan = create_plan(root, best_path, 1.0);
/*
* If creating a plan for a scrollable cursor, make sure it can run
@@ -531,32 +532,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
result->utilityStmt = parse->utilityStmt;
result->stmt_location = parse->stmt_location;
result->stmt_len = parse->stmt_len;
-
- result->jitFlags = PGJIT_NONE;
- if (jit_enabled && jit_above_cost >= 0 &&
- top_plan->total_cost > jit_above_cost)
- {
- result->jitFlags |= PGJIT_PERFORM;
-
- /*
- * Decide how much effort should be put into generating better code.
- */
- if (jit_optimize_above_cost >= 0 &&
- top_plan->total_cost > jit_optimize_above_cost)
- result->jitFlags |= PGJIT_OPT3;
- if (jit_inline_above_cost >= 0 &&
- top_plan->total_cost > jit_inline_above_cost)
- result->jitFlags |= PGJIT_INLINE;
-
- /*
- * Decide which operations should be JITed.
- */
- if (jit_expressions)
- result->jitFlags |= PGJIT_EXPR;
- if (jit_tuple_deforming)
- result->jitFlags |= PGJIT_DEFORM;
- }
-
+ result->jitFlags = glob->jitFlags;
if (glob->partition_directory != NULL)
DestroyPartitionDirectory(glob->partition_directory);
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index df4ca12919..b81d4a1828 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -233,7 +233,11 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
- plan = create_plan(subroot, best_path);
+ /*
+ * XXX we can't get an accurate est_calls to pass to create_plan here as
+ * we've not yet planned the outer query!
+ */
+ plan = create_plan(subroot, best_path, 1.0);
/* And convert to SubPlan or InitPlan format. */
result = build_subplan(root, plan, subroot, plan_params,
@@ -284,7 +288,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
AlternativeSubPlan *asplan;
/* OK, finish planning the ANY subquery */
- plan = create_plan(subroot, best_path);
+ plan = create_plan(subroot, best_path, 1.0);
/* ... and convert to SubPlan format */
hashplan = castNode(SubPlan,
@@ -997,7 +1001,7 @@ SS_process_ctes(PlannerInfo *root)
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
best_path = final_rel->cheapest_total_path;
- plan = create_plan(subroot, best_path);
+ plan = create_plan(subroot, best_path, 1.0);
/*
* Make a SubPlan node for it. This is just enough unlike
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 57c02bff45..9c57896de9 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -38,7 +38,8 @@ typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
- Plan *outer_plan);
+ Plan *outer_plan,
+ double est_calls);
typedef void (*BeginForeignScan_function) (ForeignScanState *node,
int eflags);
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 244d1e1197..6e78b06f10 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -119,6 +119,8 @@ typedef struct PlannerGlobal
int lastPlanNodeId; /* highest plan node ID assigned */
+ int jitFlags; /* OR mask of jitFlags for each plan node */
+
bool transientPlan; /* redo plan when TransactionXmin changes? */
bool dependsOnRole; /* is plan specific to current role? */
@@ -1533,6 +1535,8 @@ typedef struct MemoizePath
uint32 est_entries; /* The maximum number of entries that the
* planner expects will fit in the cache, or 0
* if unknown */
+ double est_hitratio; /* An estimate on the ratio of how many calls
+ * will result in a cache hit. */
} MemoizePath;
/*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e43e360d9b..1ea4c78bcb 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -118,6 +118,9 @@ typedef struct Plan
Cost startup_cost; /* cost expended before fetching any tuples */
Cost total_cost; /* total cost (assuming all tuples fetched) */
+ double est_calls; /* estimated number of times this plan will be
+ * (re)scanned */
+
/*
* planner's estimate of result size of this plan step
*/
@@ -135,6 +138,11 @@ typedef struct Plan
*/
bool async_capable; /* engage asynchronous-capable logic? */
+ /*
+ * information needed for jit
+ */
+ bool jit; /* jit compile for this plan node? */
+
/*
* Common structural data for all Plan types.
*/
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index c4f61c1a09..4b554d2a98 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -38,13 +38,15 @@ extern void preprocess_minmax_aggregates(PlannerInfo *root);
/*
* prototypes for plan/createplan.c
*/
-extern Plan *create_plan(PlannerInfo *root, Path *best_path);
+extern Plan *create_plan(PlannerInfo *root, Path *best_path,
+ double est_calls);
extern ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
Index scanrelid, List *fdw_exprs, List *fdw_private,
List *fdw_scan_tlist, List *fdw_recheck_quals,
Plan *outer_plan);
extern Plan *change_plan_targetlist(Plan *subplan, List *tlist,
- bool tlist_parallel_safe);
+ bool tlist_parallel_safe,
+ double est_calls);
extern Plan *materialize_finished_plan(Plan *subplan);
extern bool is_projection_capable_path(Path *path);
extern bool is_projection_capable_plan(Plan *plan);
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
index 48620edbc2..416dedee9a 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -100,6 +100,7 @@ select explain_filter('explain (analyze, buffers, format xml) select * from int8
<Total-Cost>N.N</Total-Cost> +
<Plan-Rows>N</Plan-Rows> +
<Plan-Width>N</Plan-Width> +
+ <Plan-Calls>N</Plan-Calls> +
<Actual-Startup-Time>N.N</Actual-Startup-Time> +
<Actual-Total-Time>N.N</Actual-Total-Time> +
<Actual-Rows>N</Actual-Rows> +
@@ -148,6 +149,7 @@ select explain_filter('explain (analyze, buffers, format yaml) select * from int
Total Cost: N.N +
Plan Rows: N +
Plan Width: N +
+ Plan Calls: N +
Actual Startup Time: N.N +
Actual Total Time: N.N +
Actual Rows: N +
@@ -199,6 +201,7 @@ select explain_filter('explain (buffers, format json) select * from int8_tbl i8'
"Total Cost": N.N, +
"Plan Rows": N, +
"Plan Width": N, +
+ "Plan Calls": N, +
"Shared Hit Blocks": N, +
"Shared Read Blocks": N, +
"Shared Dirtied Blocks": N, +
@@ -244,6 +247,7 @@ select explain_filter('explain (analyze, buffers, format json) select * from int
"Total Cost": N.N, +
"Plan Rows": N, +
"Plan Width": N, +
+ "Plan Calls": N, +
"Actual Startup Time": N.N, +
"Actual Total Time": N.N, +
"Actual Rows": N, +
@@ -363,6 +367,7 @@ select jsonb_pretty(
"Schema": "public", +
"Node Type": "Seq Scan", +
"Plan Rows": 0, +
+ "Plan Calls": 0, +
"Plan Width": 0, +
"Total Cost": 0.0, +
"Actual Rows": 0, +
@@ -409,6 +414,7 @@ select jsonb_pretty(
], +
"Node Type": "Sort", +
"Plan Rows": 0, +
+ "Plan Calls": 0, +
"Plan Width": 0, +
"Total Cost": 0.0, +
"Actual Rows": 0, +
@@ -452,6 +458,7 @@ select jsonb_pretty(
], +
"Node Type": "Gather Merge", +
"Plan Rows": 0, +
+ "Plan Calls": 0, +
"Plan Width": 0, +
"Total Cost": 0.0, +
"Actual Rows": 0, +
Attachments:
[text/plain] granular_jit_v2.patch (97.0K, ../../CAApHDvoq5VhV=2euyjgBN2bC8Bds9Dtr0bG7R=reeefJWKJRXA@mail.gmail.com/2-granular_jit_v2.patch)
download | inline diff:
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 4773cadec0..ba52b48783 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -129,7 +129,8 @@ static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
- Plan *outer_plan);
+ Plan *outer_plan,
+ double est_calls);
static void fileExplainForeignScan(ForeignScanState *node, ExplainState *es);
static void fileBeginForeignScan(ForeignScanState *node, int eflags);
static TupleTableSlot *fileIterateForeignScan(ForeignScanState *node);
@@ -588,7 +589,8 @@ fileGetForeignPlan(PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
- Plan *outer_plan)
+ Plan *outer_plan,
+ double est_calls)
{
Index scan_relid = baserel->relid;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 0e5771c89d..f51bf8a649 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -335,7 +335,8 @@ static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
- Plan *outer_plan);
+ Plan *outer_plan,
+ double est_calls);
static void postgresBeginForeignScan(ForeignScanState *node, int eflags);
static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node);
static void postgresReScanForeignScan(ForeignScanState *node);
@@ -1221,7 +1222,8 @@ postgresGetForeignPlan(PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
- Plan *outer_plan)
+ Plan *outer_plan,
+ double est_calls)
{
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
Index scan_relid;
@@ -1385,7 +1387,8 @@ postgresGetForeignPlan(PlannerInfo *root,
* a Result node atop the plan tree.
*/
outer_plan = change_plan_targetlist(outer_plan, fdw_scan_tlist,
- best_path->path.parallel_safe);
+ best_path->path.parallel_safe,
+ est_calls);
}
}
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 03986946a8..643680bbc6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5603,8 +5603,11 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
</term>
<listitem>
<para>
- Sets the query cost above which JIT compilation is activated, if
- enabled (see <xref linkend="jit"/>).
+ Sets the cost threshold for which a given plan node will consider
+ performing JIT compilation for itself. The <literal>total_cost</literal>
+ of a given plan node multiplied by its estimated <literal>loops</literal>
+ must exceed this value before JIT compilation is considered for the
+ plan node xref linkend="jit"/> must also be enabled.
Performing <acronym>JIT</acronym> costs planning time but can
accelerate query execution.
Setting this to <literal>-1</literal> disables JIT compilation.
@@ -5621,12 +5624,13 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
</term>
<listitem>
<para>
- Sets the query cost above which JIT compilation attempts to inline
- functions and operators. Inlining adds planning time, but can
- improve execution speed. It is not meaningful to set this to less
- than <varname>jit_above_cost</varname>.
- Setting this to <literal>-1</literal> disables inlining.
- The default is <literal>500000</literal>.
+ Sets the overall plan node cost above which JIT compilation attempts to
+ inline functions and operators. Inlining adds additional executor
+ startup overheads, but can improve performance during execution. It
+ is not meaningful to set this to less than
+ <varname>jit_above_cost</varname>. Setting this to
+ <literal>-1</literal> disables inlining. The default is
+ <literal>500000</literal>.
</para>
</listitem>
</varlistentry>
@@ -5639,13 +5643,13 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
</term>
<listitem>
<para>
- Sets the query cost above which JIT compilation applies expensive
- optimizations. Such optimization adds planning time, but can improve
- execution speed. It is not meaningful to set this to less
- than <varname>jit_above_cost</varname>, and it is unlikely to be
- beneficial to set it to more
- than <varname>jit_inline_above_cost</varname>.
- Setting this to <literal>-1</literal> disables expensive optimizations.
+ Sets the overall plan node cost above which JIT compilation applies expensive
+ optimizations. Such optimization adds executor startup overhead, but
+ can improve performance during execution. It is not meaningful to set
+ this to less than <varname>jit_above_cost</varname>, and it is
+ unlikely to be beneficial to set it to more than
+ <varname>jit_inline_above_cost</varname>.
+ Setting this to <literal>-1</literal> disables the optimization pass.
The default is <literal>500000</literal>.
</para>
</listitem>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index d2a2479822..fc36438df1 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1583,9 +1583,17 @@ ExplainNode(PlanState *planstate, List *ancestors,
{
if (es->format == EXPLAIN_FORMAT_TEXT)
{
- appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
- plan->startup_cost, plan->total_cost,
- plan->plan_rows, plan->plan_width);
+ /* only display the expected loops if it's above 1.0 */
+ if (plan->est_calls <= 1.0)
+ appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
+ plan->startup_cost, plan->total_cost,
+ plan->plan_rows, plan->plan_width);
+ else
+ appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d loops=%.0f)",
+ plan->startup_cost, plan->total_cost,
+ plan->plan_rows, plan->plan_width,
+ plan->est_calls);
+
}
else
{
@@ -1597,6 +1605,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
0, es);
ExplainPropertyInteger("Plan Width", NULL, plan->plan_width,
es);
+ ExplainPropertyFloat("Plan Calls", NULL, plan->est_calls, 0, es);
}
}
@@ -1719,6 +1728,21 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (es->verbose)
show_plan_tlist(planstate, ancestors, es);
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ /*
+ * If we did any jitting, indicate if we did any for this node or not.
+ * When format is TEXT, only do this when VERBOSE is enabled.
+ */
+ if (planstate->state->es_jit != NULL && es->verbose)
+ ExplainPropertyBool("JIT", plan->jit, es);
+ }
+ else
+ {
+ if (planstate->state->es_jit != NULL)
+ ExplainPropertyBool("JIT", plan->jit, es);
+ }
+
/* unique join */
switch (nodeTag(plan))
{
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index dda1c59b23..4854b16cfe 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -296,22 +296,8 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags)
if (estate->es_subplanstates &&
contain_subplans((Node *) exprs))
{
- int saved_jit_flags;
-
- /*
- * As these expressions are only used once, disable JIT for them.
- * This is worthwhile because it's common to insert significant
- * amounts of data via VALUES(). Note that this doesn't prevent
- * use of JIT *within* a subplan, since that's initialized
- * separately; this just affects the upper-level subexpressions.
- */
- saved_jit_flags = estate->es_jit_flags;
- estate->es_jit_flags = PGJIT_NONE;
-
scanstate->exprstatelists[i] = ExecInitExprList(exprs,
&scanstate->ss.ps);
-
- estate->es_jit_flags = saved_jit_flags;
}
i++;
}
diff --git a/src/backend/jit/README b/src/backend/jit/README
index 5427bdf215..a4aa33b79b 100644
--- a/src/backend/jit/README
+++ b/src/backend/jit/README
@@ -266,27 +266,32 @@ generation, and later compiling larger parts of queries.
When to JIT
===========
-Currently there are a number of GUCs that influence JITing:
-
-- jit_above_cost = -1, 0-DBL_MAX - all queries with a higher total cost
- get JITed, *without* optimization (expensive part), corresponding to
- -O0. This commonly already results in significant speedups if
- expression/deforming is a bottleneck (removing dynamic branches
- mostly).
-- jit_optimize_above_cost = -1, 0-DBL_MAX - all queries with a higher total cost
- get JITed, *with* optimization (expensive part).
-- jit_inline_above_cost = -1, 0-DBL_MAX - inlining is tried if query has
- higher cost.
-
-Whenever a query's total cost is above these limits, JITing is
-performed.
-
-Alternative costing models, e.g. by generating separate paths for
-parts of a query with lower cpu_* costs, are also a possibility, but
-it's doubtful the overhead of doing so is sufficient. Another
-alternative would be to count the number of times individual
+Currently, there are a number of GUCs that influence JITing. Each of these
+GUCs defines the cost that a given plan node must exceed to enable the given
+JIT feature. The costs here are calculated by multiplying the total_cost of
+the plan node by the estimated number of rescans the planner exepects the
+executor to perform on the given node. We refer to this cost as the "overall"
+cost in the text below:
+
+- jit_above_cost = -1, 0-DBL_MAX - all plan nodes which have a overall cost
+ higher than this value get JITed, *without* optimization (expensive part),
+ corresponding to -O0. This commonly already results in significant speedups
+ if expression/deforming is a bottleneck (removing dynamic branches mostly).
+- jit_optimize_above_cost = -1, 0-DBL_MAX - perform an optimization pass
+ during JIT compilation when any plan node that is eligible for JIT reaches
+ this cost.
+- jit_inline_above_cost = -1, 0-DBL_MAX - inlining is tried during JIT
+ compilation if any plan node has a higher overall cost than this value.
+
+It is important to remember that the optimize and inlining options are either
+enabled for all JIT enabled plan nodes or disabled. If a single node reaches
+the required threshold then all JIT enabled nodes will be JITed using the same
+compilation options.
+
+An alternative would be to count the number of times individual
expressions are estimated to be evaluated, and perform JITing of these
-individual expressions.
+individual expressions. This is probably more complexity than it would be
+worth.
The obvious seeming approach of JITing expressions individually after
a number of execution turns out not to work too well. Primarily
diff --git a/src/backend/jit/jit.c b/src/backend/jit/jit.c
index 18d168f1af..ade689bf22 100644
--- a/src/backend/jit/jit.c
+++ b/src/backend/jit/jit.c
@@ -172,6 +172,14 @@ jit_compile_expr(struct ExprState *state)
if (!(state->parent->state->es_jit_flags & PGJIT_EXPR))
return false;
+ /* don't jit if the plan node is missing */
+ if (state->parent->plan == NULL)
+ return false;
+
+ /* don't jit if it's not enabled for this plan node */
+ if (!state->parent->plan->jit)
+ return false;
+
/* this also takes !jit_enabled into account */
if (provider_init())
return provider.compile_expr(state);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 836f427ea8..e56db41047 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -128,6 +128,7 @@ CopyPlanFields(const Plan *from, Plan *newnode)
COPY_SCALAR_FIELD(parallel_aware);
COPY_SCALAR_FIELD(parallel_safe);
COPY_SCALAR_FIELD(async_capable);
+ COPY_SCALAR_FIELD(jit);
COPY_SCALAR_FIELD(plan_node_id);
COPY_NODE_FIELD(targetlist);
COPY_NODE_FIELD(qual);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6a02f81ad5..27360d587c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -341,6 +341,7 @@ _outPlanInfo(StringInfo str, const Plan *node)
WRITE_BOOL_FIELD(parallel_aware);
WRITE_BOOL_FIELD(parallel_safe);
WRITE_BOOL_FIELD(async_capable);
+ WRITE_BOOL_FIELD(jit);
WRITE_INT_FIELD(plan_node_id);
WRITE_NODE_FIELD(targetlist);
WRITE_NODE_FIELD(qual);
@@ -2117,6 +2118,7 @@ _outMemoizePath(StringInfo str, const MemoizePath *node)
WRITE_BOOL_FIELD(binary_mode);
WRITE_FLOAT_FIELD(calls, "%.0f");
WRITE_UINT_FIELD(est_entries);
+ WRITE_FLOAT_FIELD(est_hitratio, "%.6f");
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ddf76ac778..137ffbcdd1 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1847,6 +1847,7 @@ ReadCommonPlan(Plan *local_node)
READ_BOOL_FIELD(parallel_aware);
READ_BOOL_FIELD(parallel_safe);
READ_BOOL_FIELD(async_capable);
+ READ_BOOL_FIELD(jit);
READ_INT_FIELD(plan_node_id);
READ_NODE_FIELD(targetlist);
READ_NODE_FIELD(qual);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index b787c6f81a..c6cf109392 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -2814,6 +2814,12 @@ cost_memoize_rescan(PlannerInfo *root, MemoizePath *mpath,
/* Ensure we don't go negative */
hit_ratio = Max(hit_ratio, 0.0);
+ /*
+ * Since we've just gone to the bother of calculating the estimated hit
+ * ratio, let's store that in the MemoizePath for later use.
+ */
+ mpath->est_hitratio = hit_ratio;
+
/*
* Set the total_cost accounting for the expected cache hit ratio. We
* also add on a cpu_operator_cost to account for a cache lookup. This
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 7905bc4654..9c51103df1 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -22,6 +22,7 @@
#include "access/sysattr.h"
#include "catalog/pg_class.h"
#include "foreign/fdwapi.h"
+#include "jit/jit.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
#include "nodes/makefuncs.h"
@@ -73,95 +74,130 @@
static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path,
- int flags);
+ int flags, double est_calls);
static Plan *create_scan_plan(PlannerInfo *root, Path *best_path,
- int flags);
+ int flags, double est_calls);
+static void plan_consider_jit(PlannerInfo *root, Plan *plan);
static List *build_path_tlist(PlannerInfo *root, Path *path);
static bool use_physical_tlist(PlannerInfo *root, Path *path, int flags);
static List *get_gating_quals(PlannerInfo *root, List *quals);
static Plan *create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
- List *gating_quals);
-static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
+ List *gating_quals, double est_calls);
+static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path,
+ double est_calls);
static bool mark_async_capable_plan(Plan *plan, Path *path);
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path,
- int flags);
+ int flags, double est_calls);
static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
- int flags);
+ int flags, double est_calls);
static Result *create_group_result_plan(PlannerInfo *root,
- GroupResultPath *best_path);
-static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path);
+ GroupResultPath *best_path,
+ double est_calls);
+static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path,
+ double est_calls);
static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path,
- int flags);
+ int flags, double est_calls);
static Memoize *create_memoize_plan(PlannerInfo *root, MemoizePath *best_path,
- int flags);
+ int flags, double est_calls);
static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path,
- int flags);
-static Gather *create_gather_plan(PlannerInfo *root, GatherPath *best_path);
+ int flags, double est_calls);
+static Gather *create_gather_plan(PlannerInfo *root, GatherPath *best_path,
+ double est_calls);
static Plan *create_projection_plan(PlannerInfo *root,
ProjectionPath *best_path,
- int flags);
-static Plan *inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe);
-static Sort *create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags);
+ int flags, double est_calls);
+static Plan *inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe,
+ double est_calls);
+static Sort *create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags,
+ double est_calls);
static IncrementalSort *create_incrementalsort_plan(PlannerInfo *root,
- IncrementalSortPath *best_path, int flags);
-static Group *create_group_plan(PlannerInfo *root, GroupPath *best_path);
+ IncrementalSortPath *best_path, int flags,
+ double est_calls);
+static Group *create_group_plan(PlannerInfo *root, GroupPath *best_path,
+ double est_calls);
static Unique *create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path,
- int flags);
-static Agg *create_agg_plan(PlannerInfo *root, AggPath *best_path);
-static Plan *create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path);
-static Result *create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path);
-static WindowAgg *create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path);
+ int flags, double est_calls);
+static Agg *create_agg_plan(PlannerInfo *root, AggPath *best_path,
+ double est_calls);
+static Plan *create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path,
+ double est_calls);
+static Result *create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path,
+ double est_calls);
+static WindowAgg *create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path,
+ double est_calls);
static SetOp *create_setop_plan(PlannerInfo *root, SetOpPath *best_path,
- int flags);
-static RecursiveUnion *create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path);
+ int flags, double est_calls);
+static RecursiveUnion *create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path,
+ double est_calls);
static LockRows *create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
- int flags);
-static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path);
+ int flags, double est_calls);
+static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path,
+ double est_calls);
static Limit *create_limit_plan(PlannerInfo *root, LimitPath *best_path,
- int flags);
+ int flags, double est_calls);
static SeqScan *create_seqscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static SampleScan *create_samplescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static Scan *create_indexscan_plan(PlannerInfo *root, IndexPath *best_path,
- List *tlist, List *scan_clauses, bool indexonly);
+ List *tlist, List *scan_clauses, bool indexonly,
+ double est_calls);
static BitmapHeapScan *create_bitmap_scan_plan(PlannerInfo *root,
BitmapHeapPath *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
- List **qual, List **indexqual, List **indexECs);
+ List **qual, List **indexqual, List **indexECs,
+ double est_calls);
static void bitmap_subplan_mark_shared(Plan *plan);
static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static TidRangeScan *create_tidrangescan_plan(PlannerInfo *root,
TidRangePath *best_path,
List *tlist,
- List *scan_clauses);
+ List *scan_clauses,
+ double est_calls);
static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root,
SubqueryScanPath *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static TableFuncScan *create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static NamedTuplestoreScan *create_namedtuplestorescan_plan(PlannerInfo *root,
- Path *best_path, List *tlist, List *scan_clauses);
+ Path *best_path, List *tlist, List *scan_clauses,
+ double est_calls);
static Result *create_resultscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
- List *tlist, List *scan_clauses);
+ List *tlist, List *scan_clauses,
+ double est_calls);
static CustomScan *create_customscan_plan(PlannerInfo *root,
CustomPath *best_path,
- List *tlist, List *scan_clauses);
-static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path);
-static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path);
-static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path);
+ List *tlist, List *scan_clauses,
+ double est_calls);
+static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
+ double est_calls);
+static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
+ double est_calls);
+static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
+ double est_calls);
static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
static void fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
@@ -269,11 +305,13 @@ static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
AttrNumber **p_sortColIdx,
Oid **p_sortOperators,
Oid **p_collations,
- bool **p_nullsFirst);
+ bool **p_nullsFirst,
+ double est_calls);
static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
- Relids relids);
+ Relids relids, double est_calls);
static IncrementalSort *make_incrementalsort_from_pathkeys(Plan *lefttree,
- List *pathkeys, Relids relids, int nPresortedCols);
+ List *pathkeys, Relids relids, int nPresortedCols,
+ double est_calls);
static Sort *make_sort_from_groupcols(List *groupcls,
AttrNumber *grpColIdx,
Plan *lefttree);
@@ -314,7 +352,8 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
List *rowMarks, OnConflictExpr *onconflict,
List *mergeActionList, int epqParam);
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
- GatherMergePath *best_path);
+ GatherMergePath *best_path,
+ double est_calls);
/*
@@ -330,10 +369,12 @@ static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
*
* best_path is the best access path
*
+ * est_calls is the number of expected times that we'll (re)scan this plan
+ *
* Returns a Plan tree.
*/
Plan *
-create_plan(PlannerInfo *root, Path *best_path)
+create_plan(PlannerInfo *root, Path *best_path, double est_calls)
{
Plan *plan;
@@ -345,7 +386,7 @@ create_plan(PlannerInfo *root, Path *best_path)
root->curOuterParams = NIL;
/* Recursively process the path tree, demanding the correct tlist result */
- plan = create_plan_recurse(root, best_path, CP_EXACT_TLIST);
+ plan = create_plan_recurse(root, best_path, CP_EXACT_TLIST, est_calls);
/*
* Make sure the topmost plan node's targetlist exposes the original
@@ -384,7 +425,7 @@ create_plan(PlannerInfo *root, Path *best_path)
* Recursive guts of create_plan().
*/
static Plan *
-create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
+create_plan_recurse(PlannerInfo *root, Path *best_path, int flags, double est_calls)
{
Plan *plan;
@@ -409,136 +450,147 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
case T_NamedTuplestoreScan:
case T_ForeignScan:
case T_CustomScan:
- plan = create_scan_plan(root, best_path, flags);
+ plan = create_scan_plan(root, best_path, flags, est_calls);
break;
case T_HashJoin:
case T_MergeJoin:
case T_NestLoop:
plan = create_join_plan(root,
- (JoinPath *) best_path);
+ (JoinPath *) best_path, est_calls);
break;
case T_Append:
plan = create_append_plan(root,
(AppendPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_MergeAppend:
plan = create_merge_append_plan(root,
(MergeAppendPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_Result:
if (IsA(best_path, ProjectionPath))
{
plan = create_projection_plan(root,
(ProjectionPath *) best_path,
- flags);
+ flags, est_calls);
}
else if (IsA(best_path, MinMaxAggPath))
{
plan = (Plan *) create_minmaxagg_plan(root,
- (MinMaxAggPath *) best_path);
+ (MinMaxAggPath *) best_path,
+ est_calls);
}
else if (IsA(best_path, GroupResultPath))
{
plan = (Plan *) create_group_result_plan(root,
- (GroupResultPath *) best_path);
+ (GroupResultPath *) best_path,
+ est_calls);
}
else
{
/* Simple RTE_RESULT base relation */
Assert(IsA(best_path, Path));
- plan = create_scan_plan(root, best_path, flags);
+ plan = create_scan_plan(root, best_path, flags, est_calls);
}
break;
case T_ProjectSet:
plan = (Plan *) create_project_set_plan(root,
- (ProjectSetPath *) best_path);
+ (ProjectSetPath *) best_path,
+ est_calls);
break;
case T_Material:
plan = (Plan *) create_material_plan(root,
(MaterialPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_Memoize:
plan = (Plan *) create_memoize_plan(root,
(MemoizePath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_Unique:
if (IsA(best_path, UpperUniquePath))
{
plan = (Plan *) create_upper_unique_plan(root,
(UpperUniquePath *) best_path,
- flags);
+ flags, est_calls);
}
else
{
Assert(IsA(best_path, UniquePath));
plan = create_unique_plan(root,
(UniquePath *) best_path,
- flags);
+ flags, est_calls);
}
break;
case T_Gather:
plan = (Plan *) create_gather_plan(root,
- (GatherPath *) best_path);
+ (GatherPath *) best_path,
+ est_calls);
break;
case T_Sort:
plan = (Plan *) create_sort_plan(root,
(SortPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_IncrementalSort:
plan = (Plan *) create_incrementalsort_plan(root,
(IncrementalSortPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_Group:
plan = (Plan *) create_group_plan(root,
- (GroupPath *) best_path);
+ (GroupPath *) best_path,
+ est_calls);
break;
case T_Agg:
if (IsA(best_path, GroupingSetsPath))
plan = create_groupingsets_plan(root,
- (GroupingSetsPath *) best_path);
+ (GroupingSetsPath *) best_path,
+ est_calls);
else
{
Assert(IsA(best_path, AggPath));
plan = (Plan *) create_agg_plan(root,
- (AggPath *) best_path);
+ (AggPath *) best_path,
+ est_calls);
}
break;
case T_WindowAgg:
plan = (Plan *) create_windowagg_plan(root,
- (WindowAggPath *) best_path);
+ (WindowAggPath *) best_path,
+ est_calls);
break;
case T_SetOp:
plan = (Plan *) create_setop_plan(root,
(SetOpPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_RecursiveUnion:
plan = (Plan *) create_recursiveunion_plan(root,
- (RecursiveUnionPath *) best_path);
+ (RecursiveUnionPath *) best_path,
+ est_calls);
break;
case T_LockRows:
plan = (Plan *) create_lockrows_plan(root,
(LockRowsPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_ModifyTable:
plan = (Plan *) create_modifytable_plan(root,
- (ModifyTablePath *) best_path);
+ (ModifyTablePath *) best_path,
+ est_calls);
break;
case T_Limit:
plan = (Plan *) create_limit_plan(root,
(LimitPath *) best_path,
- flags);
+ flags, est_calls);
break;
case T_GatherMerge:
plan = (Plan *) create_gather_merge_plan(root,
- (GatherMergePath *) best_path);
+ (GatherMergePath *) best_path,
+ est_calls);
break;
default:
elog(ERROR, "unrecognized node type: %d",
@@ -547,15 +599,75 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
break;
}
+ /* See about switching on JIT for this node */
+ plan_consider_jit(root, plan);
+
return plan;
}
+static void
+plan_consider_jit(PlannerInfo *root, Plan *plan)
+{
+ int jitflags = root->glob->jitFlags;
+
+ plan->jit = false;
+
+ /*
+ * For values scans, expressions are only used once, so ensure we don't
+ * enable JIT for them.
+ */
+ if (IsA(plan, ValuesScan))
+ return;
+
+ /* Determine which JIT options to enable for this plan node */
+ if (jit_enabled && jit_above_cost >= 0)
+ {
+ Cost total_cost;
+
+ /*
+ * Take into account the number of times that we expect to rescan a
+ * given plan node. For example, subplans being invoked under the
+ * inside of a Nested Loop may be rescanned many times. JITing these
+ * may be more worthwhile.
+ */
+ total_cost = plan->total_cost * plan->est_calls;
+
+ if (total_cost > jit_above_cost)
+ {
+ plan->jit = true;
+ jitflags |= PGJIT_PERFORM;
+
+ /*
+ * Decide how much effort should be put into generating better code.
+ */
+ if (jit_optimize_above_cost >= 0 &&
+ total_cost > jit_optimize_above_cost)
+ jitflags |= PGJIT_OPT3;
+ if (jit_inline_above_cost >= 0 &&
+ total_cost > jit_inline_above_cost)
+ jitflags |= PGJIT_INLINE;
+
+ /*
+ * Decide which operations should be JITed.
+ */
+ if (jit_expressions)
+ jitflags |= PGJIT_EXPR;
+ if (jit_tuple_deforming)
+ jitflags |= PGJIT_DEFORM;
+
+ /* Record the maximum flags used by any plan node */
+ root->glob->jitFlags |= jitflags;
+ }
+ }
+}
+
/*
* create_scan_plan
* Create a scan plan for the parent relation of 'best_path'.
*/
static Plan *
-create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
+create_scan_plan(PlannerInfo *root, Path *best_path, int flags,
+ double est_calls)
{
RelOptInfo *rel = best_path->parent;
List *scan_clauses;
@@ -660,14 +772,16 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
plan = (Plan *) create_seqscan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_SampleScan:
plan = (Plan *) create_samplescan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_IndexScan:
@@ -675,7 +789,8 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
(IndexPath *) best_path,
tlist,
scan_clauses,
- false);
+ false,
+ est_calls);
break;
case T_IndexOnlyScan:
@@ -683,98 +798,112 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
(IndexPath *) best_path,
tlist,
scan_clauses,
- true);
+ true,
+ est_calls);
break;
case T_BitmapHeapScan:
plan = (Plan *) create_bitmap_scan_plan(root,
(BitmapHeapPath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_TidScan:
plan = (Plan *) create_tidscan_plan(root,
(TidPath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_TidRangeScan:
plan = (Plan *) create_tidrangescan_plan(root,
(TidRangePath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_SubqueryScan:
plan = (Plan *) create_subqueryscan_plan(root,
(SubqueryScanPath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_FunctionScan:
plan = (Plan *) create_functionscan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_TableFuncScan:
plan = (Plan *) create_tablefuncscan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_ValuesScan:
plan = (Plan *) create_valuesscan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_CteScan:
plan = (Plan *) create_ctescan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_NamedTuplestoreScan:
plan = (Plan *) create_namedtuplestorescan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_Result:
plan = (Plan *) create_resultscan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_WorkTableScan:
plan = (Plan *) create_worktablescan_plan(root,
best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_ForeignScan:
plan = (Plan *) create_foreignscan_plan(root,
(ForeignPath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
case T_CustomScan:
plan = (Plan *) create_customscan_plan(root,
(CustomPath *) best_path,
tlist,
- scan_clauses);
+ scan_clauses,
+ est_calls);
break;
default:
@@ -790,7 +919,8 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
* quals.
*/
if (gating_clauses)
- plan = create_gating_plan(root, best_path, plan, gating_clauses);
+ plan = create_gating_plan(root, best_path, plan, gating_clauses,
+ est_calls);
return plan;
}
@@ -1000,7 +1130,7 @@ get_gating_quals(PlannerInfo *root, List *quals)
*/
static Plan *
create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
- List *gating_quals)
+ List *gating_quals, double est_calls)
{
Plan *gplan;
Plan *splan;
@@ -1045,6 +1175,7 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
* gating qual being true.
*/
copy_plan_costsize(gplan, plan);
+ gplan->est_calls = clamp_row_est(est_calls);
/* Gating quals could be unsafe, so better use the Path's safety flag */
gplan->parallel_safe = path->parallel_safe;
@@ -1058,7 +1189,7 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
* inner and outer paths.
*/
static Plan *
-create_join_plan(PlannerInfo *root, JoinPath *best_path)
+create_join_plan(PlannerInfo *root, JoinPath *best_path, double est_calls)
{
Plan *plan;
List *gating_clauses;
@@ -1067,15 +1198,18 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
{
case T_MergeJoin:
plan = (Plan *) create_mergejoin_plan(root,
- (MergePath *) best_path);
+ (MergePath *) best_path,
+ est_calls);
break;
case T_HashJoin:
plan = (Plan *) create_hashjoin_plan(root,
- (HashPath *) best_path);
+ (HashPath *) best_path,
+ est_calls);
break;
case T_NestLoop:
plan = (Plan *) create_nestloop_plan(root,
- (NestPath *) best_path);
+ (NestPath *) best_path,
+ est_calls);
break;
default:
elog(ERROR, "unrecognized node type: %d",
@@ -1092,7 +1226,7 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
gating_clauses = get_gating_quals(root, best_path->joinrestrictinfo);
if (gating_clauses)
plan = create_gating_plan(root, (Path *) best_path, plan,
- gating_clauses);
+ gating_clauses, est_calls);
#ifdef NOT_USED
@@ -1107,6 +1241,8 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
get_actual_clauses(get_loc_restrictinfo(best_path))));
#endif
+ plan->est_calls = clamp_row_est(est_calls);
+
return plan;
}
@@ -1173,7 +1309,8 @@ mark_async_capable_plan(Plan *plan, Path *path)
* Returns a Plan node.
*/
static Plan *
-create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
+create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags,
+ double est_calls)
{
Append *plan;
List *tlist = build_path_tlist(root, &best_path->path);
@@ -1250,7 +1387,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
&nodeSortColIdx,
&nodeSortOperators,
&nodeCollations,
- &nodeNullsFirst);
+ &nodeNullsFirst,
+ est_calls);
tlist_was_changed = (orig_tlist_length != list_length(plan->plan.targetlist));
}
@@ -1266,7 +1404,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
Plan *subplan;
/* Must insist that all children return the same tlist */
- subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
+ subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST, est_calls);
/*
* For ordered Appends, we must insert a Sort node if subplan isn't
@@ -1294,7 +1432,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ est_calls);
/*
* Check that we got the same sort key information. We just
@@ -1370,6 +1509,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
plan->part_prune_info = partpruneinfo;
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
/*
* If prepare_sort_from_pathkeys added sort columns, but we were told to
@@ -1381,7 +1521,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
tlist = list_truncate(list_copy(plan->plan.targetlist),
orig_tlist_length);
return inject_projection_plan((Plan *) plan, tlist,
- plan->plan.parallel_safe);
+ plan->plan.parallel_safe, est_calls);
}
else
return (Plan *) plan;
@@ -1396,7 +1536,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
*/
static Plan *
create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
- int flags)
+ int flags, double est_calls)
{
MergeAppend *node = makeNode(MergeAppend);
Plan *plan = &node->plan;
@@ -1416,6 +1556,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
* child plans, to make cross-checking the sort info easier.
*/
copy_generic_path_info(plan, (Path *) best_path);
+ plan->est_calls = clamp_row_est(est_calls);
plan->targetlist = tlist;
plan->qual = NIL;
plan->lefttree = NULL;
@@ -1436,7 +1577,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
&node->sortColIdx,
&node->sortOperators,
&node->collations,
- &node->nullsFirst);
+ &node->nullsFirst,
+ est_calls);
tlist_was_changed = (orig_tlist_length != list_length(plan->targetlist));
/*
@@ -1456,7 +1598,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
/* Build the child plan */
/* Must insist that all children return the same tlist */
- subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
+ subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST, est_calls);
/* Compute sort column info, and adjust subplan's tlist as needed */
subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
@@ -1467,7 +1609,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ est_calls);
/*
* Check that we got the same sort key information. We just Assert
@@ -1539,7 +1682,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
if (tlist_was_changed && (flags & (CP_EXACT_TLIST | CP_SMALL_TLIST)))
{
tlist = list_truncate(list_copy(plan->targetlist), orig_tlist_length);
- return inject_projection_plan(plan, tlist, plan->parallel_safe);
+ return inject_projection_plan(plan, tlist, plan->parallel_safe,
+ est_calls);
}
else
return plan;
@@ -1553,7 +1697,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
* Returns a Plan node.
*/
static Result *
-create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
+create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path,
+ double est_calls)
{
Result *plan;
List *tlist;
@@ -1567,6 +1712,7 @@ create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
plan = make_result(tlist, (Node *) quals, NULL);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -1578,20 +1724,22 @@ create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
* Returns a Plan node.
*/
static ProjectSet *
-create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path)
+create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path,
+ double est_calls)
{
ProjectSet *plan;
Plan *subplan;
List *tlist;
/* Since we intend to project, we don't need to constrain child tlist */
- subplan = create_plan_recurse(root, best_path->subpath, 0);
+ subplan = create_plan_recurse(root, best_path->subpath, 0, est_calls);
tlist = build_path_tlist(root, &best_path->path);
plan = make_project_set(tlist, subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -1604,7 +1752,8 @@ create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path)
* Returns a Plan node.
*/
static Material *
-create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags)
+create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags,
+ double est_calls)
{
Material *plan;
Plan *subplan;
@@ -1612,14 +1761,21 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags)
/*
* We don't want any excess columns in the materialized tuples, so request
* a smaller tlist. Otherwise, since Material doesn't project, tlist
- * requirements pass through.
+ * requirements pass through. Here we also don't propagate the est_calls
+ * to the subplan. We assume that the Material node will only call its
+ * subplan once and then returned the cached version on each subsequent
+ * execution. This might not be true when a parameter change causes the
+ * Material node to have to rescan, but that's hard to estimate here and
+ * the current usages of est_calls does not seem important enough to
+ * warrant expending too much effort trying to calculate this.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- flags | CP_SMALL_TLIST);
+ flags | CP_SMALL_TLIST, 1.0);
plan = make_material(subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -1632,7 +1788,8 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags)
* Returns a Plan node.
*/
static Memoize *
-create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags)
+create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags,
+ double est_calls)
{
Memoize *plan;
Bitmapset *keyparamids;
@@ -1645,8 +1802,13 @@ create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags)
int nkeys;
int i;
+ /*
+ * est_calls must take into account the expected hit ratio of the cache.
+ * We'll only be calling the subplan when it's a cache miss.
+ */
subplan = create_plan_recurse(root, best_path->subpath,
- flags | CP_SMALL_TLIST);
+ flags | CP_SMALL_TLIST,
+ est_calls * (1.0 - best_path->est_hitratio));
param_exprs = (List *) replace_nestloop_params(root, (Node *)
best_path->param_exprs);
@@ -1674,6 +1836,7 @@ create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags)
best_path->est_entries, keyparamids);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -1686,7 +1849,8 @@ create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags)
* Returns a Plan node.
*/
static Plan *
-create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
+create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags,
+ double est_calls)
{
Plan *plan;
Plan *subplan;
@@ -1702,7 +1866,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
ListCell *l;
/* Unique doesn't project, so tlist requirements pass through */
- subplan = create_plan_recurse(root, best_path->subpath, flags);
+ subplan = create_plan_recurse(root, best_path->subpath, flags, est_calls);
/* Done if we don't need to do any actual unique-ifying */
if (best_path->umethod == UNIQUE_PATH_NOOP)
@@ -1753,7 +1917,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
/* Use change_plan_targetlist in case we need to insert a Result node */
if (newitems || best_path->umethod == UNIQUE_PATH_SORT)
subplan = change_plan_targetlist(subplan, newtlist,
- best_path->path.parallel_safe);
+ best_path->path.parallel_safe,
+ est_calls);
/*
* Build control information showing which subplan output columns are to
@@ -1874,6 +2039,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
/* Copy cost data from Path to Plan */
copy_generic_path_info(plan, &best_path->path);
+ plan->est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -1885,7 +2051,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
* for its subpaths.
*/
static Gather *
-create_gather_plan(PlannerInfo *root, GatherPath *best_path)
+create_gather_plan(PlannerInfo *root, GatherPath *best_path, double est_calls)
{
Gather *gather_plan;
Plan *subplan;
@@ -1897,7 +2063,8 @@ create_gather_plan(PlannerInfo *root, GatherPath *best_path)
* can't travel through a tuple queue because it uses MinimalTuple
* representation).
*/
- subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST);
+ subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST,
+ est_calls);
tlist = build_path_tlist(root, &best_path->path);
@@ -1909,6 +2076,7 @@ create_gather_plan(PlannerInfo *root, GatherPath *best_path)
subplan);
copy_generic_path_info(&gather_plan->plan, &best_path->path);
+ gather_plan->plan.est_calls = clamp_row_est(est_calls);
/* use parallel mode for parallel plans. */
root->glob->parallelModeNeeded = true;
@@ -1923,7 +2091,8 @@ create_gather_plan(PlannerInfo *root, GatherPath *best_path)
* plans for its subpaths.
*/
static GatherMerge *
-create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
+create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path,
+ double est_calls)
{
GatherMerge *gm_plan;
Plan *subplan;
@@ -1931,13 +2100,15 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
List *tlist = build_path_tlist(root, &best_path->path);
/* As with Gather, project away columns in the workers. */
- subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST);
+ subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST,
+ est_calls);
/* Create a shell for a GatherMerge plan. */
gm_plan = makeNode(GatherMerge);
gm_plan->plan.targetlist = tlist;
gm_plan->num_workers = best_path->num_workers;
copy_generic_path_info(&gm_plan->plan, &best_path->path);
+ gm_plan->plan.est_calls = clamp_row_est(est_calls);
/* Assign the rescan Param. */
gm_plan->rescan_param = assign_special_exec_param(root);
@@ -1954,7 +2125,8 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
&gm_plan->sortColIdx,
&gm_plan->sortOperators,
&gm_plan->collations,
- &gm_plan->nullsFirst);
+ &gm_plan->nullsFirst,
+ est_calls);
/*
@@ -1984,7 +2156,8 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
* but sometimes we can just let the subplan do the work.
*/
static Plan *
-create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
+create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags,
+ double est_calls)
{
Plan *plan;
Plan *subplan;
@@ -2011,7 +2184,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
* actually need to project. However, we may still need to ensure
* proper sortgroupref labels, if the caller cares about those.
*/
- subplan = create_plan_recurse(root, best_path->subpath, 0);
+ subplan = create_plan_recurse(root, best_path->subpath, 0, est_calls);
tlist = subplan->targetlist;
if (flags & CP_LABEL_TLIST)
apply_pathtarget_labeling_to_tlist(tlist,
@@ -2026,7 +2199,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
* produces.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- CP_IGNORE_TLIST);
+ CP_IGNORE_TLIST, est_calls);
Assert(is_projection_capable_plan(subplan));
tlist = build_path_tlist(root, &best_path->path);
}
@@ -2036,7 +2209,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
* It looks like we need a result node, unless by good fortune the
* requested tlist is exactly the one the child wants to produce.
*/
- subplan = create_plan_recurse(root, best_path->subpath, 0);
+ subplan = create_plan_recurse(root, best_path->subpath, 0, est_calls);
tlist = build_path_tlist(root, &best_path->path);
needs_result_node = !tlist_same_exprs(tlist, subplan->targetlist);
}
@@ -2069,6 +2242,7 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
plan = (Plan *) make_result(tlist, NULL, subplan);
copy_generic_path_info(plan, (Path *) best_path);
+ plan->est_calls = clamp_row_est(est_calls);
}
return plan;
@@ -2086,7 +2260,8 @@ create_projection_plan(PlannerInfo *root, ProjectionPath *best_path, int flags)
* to apply (since the tlist might be unsafe even if the child plan is safe).
*/
static Plan *
-inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe)
+inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe,
+ double est_calls)
{
Plan *plan;
@@ -2100,6 +2275,7 @@ inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe)
* consistent not more so. Hence, just copy the subplan's cost.
*/
copy_plan_costsize(plan, subplan);
+ plan->est_calls = clamp_row_est(est_calls);
plan->parallel_safe = parallel_safe;
return plan;
@@ -2118,7 +2294,8 @@ inject_projection_plan(Plan *subplan, List *tlist, bool parallel_safe)
* flag of the FDW's own Path node.
*/
Plan *
-change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe)
+change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe,
+ double est_calls)
{
/*
* If the top plan node can't do projections and its existing target list
@@ -2129,7 +2306,7 @@ change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe)
!tlist_same_exprs(tlist, subplan->targetlist))
subplan = inject_projection_plan(subplan, tlist,
subplan->parallel_safe &&
- tlist_parallel_safe);
+ tlist_parallel_safe, est_calls);
else
{
/* Else we can just replace the plan node's tlist */
@@ -2146,7 +2323,8 @@ change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe)
* for its subpaths.
*/
static Sort *
-create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
+create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags,
+ double est_calls)
{
Sort *plan;
Plan *subplan;
@@ -2157,7 +2335,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
* requirements pass through.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- flags | CP_SMALL_TLIST);
+ flags | CP_SMALL_TLIST, est_calls);
/*
* make_sort_from_pathkeys indirectly calls find_ec_member_matching_expr,
@@ -2167,9 +2345,11 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
*/
plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys,
IS_OTHER_REL(best_path->subpath->parent) ?
- best_path->path.parent->relids : NULL);
+ best_path->path.parent->relids : NULL,
+ est_calls);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2181,21 +2361,22 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
*/
static IncrementalSort *
create_incrementalsort_plan(PlannerInfo *root, IncrementalSortPath *best_path,
- int flags)
+ int flags, double est_calls)
{
IncrementalSort *plan;
Plan *subplan;
/* See comments in create_sort_plan() above */
subplan = create_plan_recurse(root, best_path->spath.subpath,
- flags | CP_SMALL_TLIST);
+ flags | CP_SMALL_TLIST, est_calls);
plan = make_incrementalsort_from_pathkeys(subplan,
best_path->spath.path.pathkeys,
IS_OTHER_REL(best_path->spath.subpath->parent) ?
best_path->spath.path.parent->relids : NULL,
- best_path->nPresortedCols);
+ best_path->nPresortedCols, est_calls);
copy_generic_path_info(&plan->sort.plan, (Path *) best_path);
+ plan->sort.plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2207,7 +2388,7 @@ create_incrementalsort_plan(PlannerInfo *root, IncrementalSortPath *best_path,
* for its subpaths.
*/
static Group *
-create_group_plan(PlannerInfo *root, GroupPath *best_path)
+create_group_plan(PlannerInfo *root, GroupPath *best_path, double est_calls)
{
Group *plan;
Plan *subplan;
@@ -2218,7 +2399,8 @@ create_group_plan(PlannerInfo *root, GroupPath *best_path)
* Group can project, so no need to be terribly picky about child tlist,
* but we do need grouping columns to be available
*/
- subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
+ subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST,
+ est_calls);
tlist = build_path_tlist(root, &best_path->path);
@@ -2235,6 +2417,7 @@ create_group_plan(PlannerInfo *root, GroupPath *best_path)
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2246,7 +2429,8 @@ create_group_plan(PlannerInfo *root, GroupPath *best_path)
* for its subpaths.
*/
static Unique *
-create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flags)
+create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flags,
+ double est_calls)
{
Unique *plan;
Plan *subplan;
@@ -2256,13 +2440,14 @@ create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flag
* need grouping columns to be labeled.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- flags | CP_LABEL_TLIST);
+ flags | CP_LABEL_TLIST, est_calls);
plan = make_unique_from_pathkeys(subplan,
best_path->path.pathkeys,
best_path->numkeys);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2274,7 +2459,7 @@ create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flag
* for its subpaths.
*/
static Agg *
-create_agg_plan(PlannerInfo *root, AggPath *best_path)
+create_agg_plan(PlannerInfo *root, AggPath *best_path, double est_calls)
{
Agg *plan;
Plan *subplan;
@@ -2285,7 +2470,8 @@ create_agg_plan(PlannerInfo *root, AggPath *best_path)
* Agg can project, so no need to be terribly picky about child tlist, but
* we do need grouping columns to be available
*/
- subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
+ subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST,
+ est_calls);
tlist = build_path_tlist(root, &best_path->path);
@@ -2307,6 +2493,7 @@ create_agg_plan(PlannerInfo *root, AggPath *best_path)
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2358,7 +2545,8 @@ remap_groupColIdx(PlannerInfo *root, List *groupClause)
* Returns a Plan node.
*/
static Plan *
-create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
+create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path,
+ double est_calls)
{
Agg *plan;
Plan *subplan;
@@ -2376,7 +2564,8 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
* Agg can project, so no need to be terribly picky about child tlist, but
* we do need grouping columns to be available
*/
- subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
+ subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST,
+ est_calls);
/*
* Compute the mapping from tleSortGroupRef to column index in the child's
@@ -2471,7 +2660,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
sort_plan->targetlist = NIL;
sort_plan->lefttree = NULL;
}
-
+ /* XXX do we need to record est_calls here? */
chain = lappend(chain, agg_plan);
}
}
@@ -2504,6 +2693,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
/* Copy cost data from Path to Plan */
copy_generic_path_info(&plan->plan, &best_path->path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
}
return (Plan *) plan;
@@ -2516,7 +2706,8 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
* for its subpaths.
*/
static Result *
-create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
+create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path,
+ double est_calls)
{
Result *plan;
List *tlist;
@@ -2536,7 +2727,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
* Since we are entering a different planner context (subroot),
* recurse to create_plan not create_plan_recurse.
*/
- plan = create_plan(subroot, mminfo->path);
+ plan = create_plan(subroot, mminfo->path, est_calls);
plan = (Plan *) make_limit(plan,
subparse->limitOffset,
@@ -2562,6 +2753,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
plan = make_result(tlist, (Node *) best_path->quals, NULL);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
/*
* During setrefs.c, we'll need to replace references to the Agg nodes
@@ -2582,7 +2774,8 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
* for its subpaths.
*/
static WindowAgg *
-create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
+create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path,
+ double est_calls)
{
WindowAgg *plan;
WindowClause *wc = best_path->winclause;
@@ -2607,7 +2800,7 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
* course need grouping columns to be available.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- CP_LABEL_TLIST | CP_SMALL_TLIST);
+ CP_LABEL_TLIST | CP_SMALL_TLIST, est_calls);
tlist = build_path_tlist(root, &best_path->path);
@@ -2679,6 +2872,7 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2690,7 +2884,8 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
* for its subpaths.
*/
static SetOp *
-create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags)
+create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags,
+ double est_calls)
{
SetOp *plan;
Plan *subplan;
@@ -2701,7 +2896,7 @@ create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags)
* need grouping columns to be labeled.
*/
subplan = create_plan_recurse(root, best_path->subpath,
- flags | CP_LABEL_TLIST);
+ flags | CP_LABEL_TLIST, est_calls);
/* Convert numGroups to long int --- but 'ware overflow! */
numGroups = (long) Min(best_path->numGroups, (double) LONG_MAX);
@@ -2715,6 +2910,7 @@ create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags)
numGroups);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2726,7 +2922,8 @@ create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags)
* for its subpaths.
*/
static RecursiveUnion *
-create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path)
+create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path,
+ double est_calls)
{
RecursiveUnion *plan;
Plan *leftplan;
@@ -2735,8 +2932,10 @@ create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path)
long numGroups;
/* Need both children to produce same tlist, so force it */
- leftplan = create_plan_recurse(root, best_path->leftpath, CP_EXACT_TLIST);
- rightplan = create_plan_recurse(root, best_path->rightpath, CP_EXACT_TLIST);
+ leftplan = create_plan_recurse(root, best_path->leftpath, CP_EXACT_TLIST,
+ est_calls);
+ rightplan = create_plan_recurse(root, best_path->rightpath, CP_EXACT_TLIST,
+ est_calls);
tlist = build_path_tlist(root, &best_path->path);
@@ -2751,6 +2950,7 @@ create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path)
numGroups);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2763,17 +2963,18 @@ create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path)
*/
static LockRows *
create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
- int flags)
+ int flags, double est_calls)
{
LockRows *plan;
Plan *subplan;
/* LockRows doesn't project, so tlist requirements pass through */
- subplan = create_plan_recurse(root, best_path->subpath, flags);
+ subplan = create_plan_recurse(root, best_path->subpath, flags, est_calls);
plan = make_lockrows(subplan, best_path->rowMarks, best_path->epqParam);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2785,14 +2986,15 @@ create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
* Returns a Plan node.
*/
static ModifyTable *
-create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
+create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path,
+ double est_calls)
{
ModifyTable *plan;
Path *subpath = best_path->subpath;
Plan *subplan;
/* Subplan must produce exactly the specified tlist */
- subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
+ subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST, est_calls);
/* Transfer resname/resjunk labeling, too, to keep executor happy */
apply_tlist_labeling(subplan->targetlist, root->processed_tlist);
@@ -2814,6 +3016,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
best_path->epqParam);
copy_generic_path_info(&plan->plan, &best_path->path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2825,7 +3028,8 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
* for its subpaths.
*/
static Limit *
-create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
+create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags,
+ double est_calls)
{
Limit *plan;
Plan *subplan;
@@ -2835,7 +3039,7 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
Oid *uniqCollations = NULL;
/* Limit doesn't project, so tlist requirements pass through */
- subplan = create_plan_recurse(root, best_path->subpath, flags);
+ subplan = create_plan_recurse(root, best_path->subpath, flags, est_calls);
/* Extract information necessary for comparing rows for WITH TIES. */
if (best_path->limitOption == LIMIT_OPTION_WITH_TIES)
@@ -2868,6 +3072,7 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
numUniqkeys, uniqColIdx, uniqOperators, uniqCollations);
copy_generic_path_info(&plan->plan, (Path *) best_path);
+ plan->plan.est_calls = clamp_row_est(est_calls);
return plan;
}
@@ -2887,7 +3092,7 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
*/
static SeqScan *
create_seqscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
SeqScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -2914,6 +3119,7 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
scan_relid);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -2925,7 +3131,7 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
*/
static SampleScan *
create_samplescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
SampleScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -2960,6 +3166,7 @@ create_samplescan_plan(PlannerInfo *root, Path *best_path,
tsc);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -2979,7 +3186,7 @@ create_indexscan_plan(PlannerInfo *root,
IndexPath *best_path,
List *tlist,
List *scan_clauses,
- bool indexonly)
+ bool indexonly, double est_calls)
{
Scan *scan_plan;
List *indexclauses = best_path->indexclauses;
@@ -3158,6 +3365,7 @@ create_indexscan_plan(PlannerInfo *root,
best_path->indexscandir);
copy_generic_path_info(&scan_plan->plan, &best_path->path);
+ scan_plan->plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3171,7 +3379,7 @@ static BitmapHeapScan *
create_bitmap_scan_plan(PlannerInfo *root,
BitmapHeapPath *best_path,
List *tlist,
- List *scan_clauses)
+ List *scan_clauses, double est_calls)
{
Index baserelid = best_path->path.parent->relid;
Plan *bitmapqualplan;
@@ -3189,7 +3397,7 @@ create_bitmap_scan_plan(PlannerInfo *root,
/* Process the bitmapqual tree into a Plan tree and qual lists */
bitmapqualplan = create_bitmap_subplan(root, best_path->bitmapqual,
&bitmapqualorig, &indexquals,
- &indexECs);
+ &indexECs, est_calls);
if (best_path->path.parallel_aware)
bitmap_subplan_mark_shared(bitmapqualplan);
@@ -3273,6 +3481,7 @@ create_bitmap_scan_plan(PlannerInfo *root,
baserelid);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3299,7 +3508,8 @@ create_bitmap_scan_plan(PlannerInfo *root,
*/
static Plan *
create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
- List **qual, List **indexqual, List **indexECs)
+ List **qual, List **indexqual, List **indexECs,
+ double est_calls)
{
Plan *plan;
@@ -3328,7 +3538,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
&subqual, &subindexqual,
- &subindexEC);
+ &subindexEC, est_calls);
subplans = lappend(subplans, subplan);
subquals = list_concat_unique(subquals, subqual);
subindexquals = list_concat_unique(subindexquals, subindexqual);
@@ -3375,7 +3585,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
&subqual, &subindexqual,
- &subindexEC);
+ &subindexEC, est_calls);
subplans = lappend(subplans, subplan);
if (subqual == NIL)
const_true_subqual = true;
@@ -3440,7 +3650,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
/* Use the regular indexscan plan build machinery... */
iscan = castNode(IndexScan,
create_indexscan_plan(root, ipath,
- NIL, NIL, false));
+ NIL, NIL, false, est_calls));
/* then convert to a bitmap indexscan */
plan = (Plan *) make_bitmap_indexscan(iscan->scan.scanrelid,
iscan->indexid,
@@ -3507,7 +3717,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
*/
static TidScan *
create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
TidScan *scan_plan;
Index scan_relid = best_path->path.parent->relid;
@@ -3593,6 +3803,7 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
tidquals);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3604,7 +3815,7 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
*/
static TidRangeScan *
create_tidrangescan_plan(PlannerInfo *root, TidRangePath *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
TidRangeScan *scan_plan;
Index scan_relid = best_path->path.parent->relid;
@@ -3658,6 +3869,7 @@ create_tidrangescan_plan(PlannerInfo *root, TidRangePath *best_path,
tidrangequals);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3669,7 +3881,7 @@ create_tidrangescan_plan(PlannerInfo *root, TidRangePath *best_path,
*/
static SubqueryScan *
create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
SubqueryScan *scan_plan;
RelOptInfo *rel = best_path->path.parent;
@@ -3685,7 +3897,7 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
* a different planner context (subroot), recurse to create_plan not
* create_plan_recurse.
*/
- subplan = create_plan(rel->subroot, best_path->subpath);
+ subplan = create_plan(rel->subroot, best_path->subpath, est_calls);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
@@ -3708,6 +3920,7 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
subplan);
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3719,7 +3932,7 @@ create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
*/
static FunctionScan *
create_functionscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
FunctionScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -3751,6 +3964,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
functions, rte->funcordinality);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3762,7 +3976,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
*/
static TableFuncScan *
create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
TableFuncScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -3794,6 +4008,7 @@ create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
tablefunc);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3805,7 +4020,7 @@ create_tablefuncscan_plan(PlannerInfo *root, Path *best_path,
*/
static ValuesScan *
create_valuesscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
ValuesScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -3838,6 +4053,7 @@ create_valuesscan_plan(PlannerInfo *root, Path *best_path,
values_lists);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3849,7 +4065,7 @@ create_valuesscan_plan(PlannerInfo *root, Path *best_path,
*/
static CteScan *
create_ctescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
CteScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -3932,6 +4148,7 @@ create_ctescan_plan(PlannerInfo *root, Path *best_path,
plan_id, cte_param_id);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3944,7 +4161,8 @@ create_ctescan_plan(PlannerInfo *root, Path *best_path,
*/
static NamedTuplestoreScan *
create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses,
+ double est_calls)
{
NamedTuplestoreScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -3971,6 +4189,7 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
rte->enrname);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -3983,7 +4202,7 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
*/
static Result *
create_resultscan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
Result *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -4009,6 +4228,7 @@ create_resultscan_plan(PlannerInfo *root, Path *best_path,
scan_plan = make_result(tlist, (Node *) scan_clauses, NULL);
copy_generic_path_info(&scan_plan->plan, best_path);
+ scan_plan->plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -4020,7 +4240,7 @@ create_resultscan_plan(PlannerInfo *root, Path *best_path,
*/
static WorkTableScan *
create_worktablescan_plan(PlannerInfo *root, Path *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
WorkTableScan *scan_plan;
Index scan_relid = best_path->parent->relid;
@@ -4069,6 +4289,7 @@ create_worktablescan_plan(PlannerInfo *root, Path *best_path,
cteroot->wt_param_id);
copy_generic_path_info(&scan_plan->scan.plan, best_path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
return scan_plan;
}
@@ -4080,7 +4301,7 @@ create_worktablescan_plan(PlannerInfo *root, Path *best_path,
*/
static ForeignScan *
create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
ForeignScan *scan_plan;
RelOptInfo *rel = best_path->path.parent;
@@ -4093,7 +4314,7 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
/* transform the child path if any */
if (best_path->fdw_outerpath)
outer_plan = create_plan_recurse(root, best_path->fdw_outerpath,
- CP_EXACT_TLIST);
+ CP_EXACT_TLIST, est_calls);
/*
* If we're scanning a base relation, fetch its OID. (Irrelevant if
@@ -4125,10 +4346,11 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rel_oid,
best_path,
tlist, scan_clauses,
- outer_plan);
+ outer_plan, est_calls);
/* Copy cost data from Path to Plan; no need to make FDW do this */
copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
+ scan_plan->scan.plan.est_calls = clamp_row_est(est_calls);
/* Copy foreign server OID; likewise, no need to make FDW do this */
scan_plan->fs_server = rel->serverid;
@@ -4224,7 +4446,7 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
*/
static CustomScan *
create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
- List *tlist, List *scan_clauses)
+ List *tlist, List *scan_clauses, double est_calls)
{
CustomScan *cplan;
RelOptInfo *rel = best_path->path.parent;
@@ -4235,7 +4457,7 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
foreach(lc, best_path->custom_paths)
{
Plan *plan = create_plan_recurse(root, (Path *) lfirst(lc),
- CP_EXACT_TLIST);
+ CP_EXACT_TLIST, est_calls);
custom_plans = lappend(custom_plans, plan);
}
@@ -4263,6 +4485,7 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
* do this
*/
copy_generic_path_info(&cplan->scan.plan, &best_path->path);
+ cplan->scan.plan.est_calls = clamp_row_est(est_calls);
/* Likewise, copy the relids that are represented by this custom scan */
cplan->custom_relids = best_path->path.parent->relids;
@@ -4295,7 +4518,7 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
static NestLoop *
create_nestloop_plan(PlannerInfo *root,
- NestPath *best_path)
+ NestPath *best_path, double est_calls)
{
NestLoop *join_plan;
Plan *outer_plan;
@@ -4309,13 +4532,15 @@ create_nestloop_plan(PlannerInfo *root,
Relids saveOuterRels = root->curOuterRels;
/* NestLoop can project, so no need to be picky about child tlists */
- outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath, 0);
+ outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath, 0,
+ est_calls);
/* For a nestloop, include outer relids in curOuterRels for inner side */
root->curOuterRels = bms_union(root->curOuterRels,
best_path->jpath.outerjoinpath->parent->relids);
- inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath, 0);
+ inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath, 0,
+ est_calls * outer_plan->plan_rows);
/* Restore curOuterRels */
bms_free(root->curOuterRels);
@@ -4365,13 +4590,14 @@ create_nestloop_plan(PlannerInfo *root,
best_path->jpath.inner_unique);
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
+ join_plan->join.plan.est_calls = clamp_row_est(est_calls);
return join_plan;
}
static MergeJoin *
create_mergejoin_plan(PlannerInfo *root,
- MergePath *best_path)
+ MergePath *best_path, double est_calls)
{
MergeJoin *join_plan;
Plan *outer_plan;
@@ -4403,10 +4629,12 @@ create_mergejoin_plan(PlannerInfo *root,
* necessary.
*/
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
- (best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0);
+ (best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0,
+ est_calls);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
- (best_path->innersortkeys != NIL) ? CP_SMALL_TLIST : 0);
+ (best_path->innersortkeys != NIL) ? CP_SMALL_TLIST : 0,
+ est_calls);
/* Sort join qual clauses into best execution order */
/* NB: do NOT reorder the mergeclauses */
@@ -4462,7 +4690,7 @@ create_mergejoin_plan(PlannerInfo *root,
Relids outer_relids = outer_path->parent->relids;
Sort *sort = make_sort_from_pathkeys(outer_plan,
best_path->outersortkeys,
- outer_relids);
+ outer_relids, est_calls);
label_sort_with_costsize(root, sort, -1.0);
outer_plan = (Plan *) sort;
@@ -4476,7 +4704,7 @@ create_mergejoin_plan(PlannerInfo *root,
Relids inner_relids = inner_path->parent->relids;
Sort *sort = make_sort_from_pathkeys(inner_plan,
best_path->innersortkeys,
- inner_relids);
+ inner_relids, est_calls);
label_sort_with_costsize(root, sort, -1.0);
inner_plan = (Plan *) sort;
@@ -4499,6 +4727,7 @@ create_mergejoin_plan(PlannerInfo *root,
* sync with final_cost_mergejoin.)
*/
copy_plan_costsize(matplan, inner_plan);
+ inner_plan->est_calls = clamp_row_est(est_calls);
matplan->total_cost += cpu_operator_cost * matplan->plan_rows;
inner_plan = matplan;
@@ -4672,13 +4901,14 @@ create_mergejoin_plan(PlannerInfo *root,
/* Costs of sort and material steps are included in path cost already */
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
+ join_plan->join.plan.est_calls = clamp_row_est(est_calls);
return join_plan;
}
static HashJoin *
create_hashjoin_plan(PlannerInfo *root,
- HashPath *best_path)
+ HashPath *best_path, double est_calls)
{
HashJoin *join_plan;
Hash *hash_plan;
@@ -4705,10 +4935,11 @@ create_hashjoin_plan(PlannerInfo *root,
* that we don't put extra data in the outer batch files.
*/
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
- (best_path->num_batches > 1) ? CP_SMALL_TLIST : 0);
+ (best_path->num_batches > 1) ? CP_SMALL_TLIST : 0,
+ est_calls);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
- CP_SMALL_TLIST);
+ CP_SMALL_TLIST, est_calls);
/* Sort join qual clauses into best execution order */
joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo);
@@ -4845,6 +5076,7 @@ create_hashjoin_plan(PlannerInfo *root,
best_path->jpath.inner_unique);
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
+ join_plan->join.plan.est_calls = clamp_row_est(est_calls);
return join_plan;
}
@@ -6106,7 +6338,8 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
AttrNumber **p_sortColIdx,
Oid **p_sortOperators,
Oid **p_collations,
- bool **p_nullsFirst)
+ bool **p_nullsFirst,
+ double est_calls)
{
List *tlist = lefttree->targetlist;
ListCell *i;
@@ -6226,7 +6459,8 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
/* copy needed so we don't modify input's tlist below */
tlist = copyObject(tlist);
lefttree = inject_projection_plan(lefttree, tlist,
- lefttree->parallel_safe);
+ lefttree->parallel_safe,
+ est_calls);
}
/* Don't bother testing is_projection_capable_plan again */
@@ -6283,7 +6517,8 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
* 'relids' is the set of relations required by prepare_sort_from_pathkeys()
*/
static Sort *
-make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
+make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids,
+ double est_calls)
{
int numsortkeys;
AttrNumber *sortColIdx;
@@ -6300,7 +6535,8 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ est_calls);
/* Now build the Sort node */
return make_sort(lefttree, numsortkeys,
@@ -6319,7 +6555,8 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
*/
static IncrementalSort *
make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
- Relids relids, int nPresortedCols)
+ Relids relids, int nPresortedCols,
+ double est_calls)
{
int numsortkeys;
AttrNumber *sortColIdx;
@@ -6336,7 +6573,8 @@ make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ est_calls);
/* Now build the Sort node */
return make_incrementalsort(lefttree, numsortkeys, nPresortedCols,
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 9a4accb4d9..7b2552c0e4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -314,6 +314,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
glob->lastPHId = 0;
glob->lastRowMarkId = 0;
glob->lastPlanNodeId = 0;
+ glob->jitFlags = PGJIT_NONE;
glob->transientPlan = false;
glob->dependsOnRole = false;
@@ -410,7 +411,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
- top_plan = create_plan(root, best_path);
+ top_plan = create_plan(root, best_path, 1.0);
/*
* If creating a plan for a scrollable cursor, make sure it can run
@@ -531,32 +532,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
result->utilityStmt = parse->utilityStmt;
result->stmt_location = parse->stmt_location;
result->stmt_len = parse->stmt_len;
-
- result->jitFlags = PGJIT_NONE;
- if (jit_enabled && jit_above_cost >= 0 &&
- top_plan->total_cost > jit_above_cost)
- {
- result->jitFlags |= PGJIT_PERFORM;
-
- /*
- * Decide how much effort should be put into generating better code.
- */
- if (jit_optimize_above_cost >= 0 &&
- top_plan->total_cost > jit_optimize_above_cost)
- result->jitFlags |= PGJIT_OPT3;
- if (jit_inline_above_cost >= 0 &&
- top_plan->total_cost > jit_inline_above_cost)
- result->jitFlags |= PGJIT_INLINE;
-
- /*
- * Decide which operations should be JITed.
- */
- if (jit_expressions)
- result->jitFlags |= PGJIT_EXPR;
- if (jit_tuple_deforming)
- result->jitFlags |= PGJIT_DEFORM;
- }
-
+ result->jitFlags = glob->jitFlags;
if (glob->partition_directory != NULL)
DestroyPartitionDirectory(glob->partition_directory);
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index df4ca12919..b81d4a1828 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -233,7 +233,11 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
- plan = create_plan(subroot, best_path);
+ /*
+ * XXX we can't get an accurate est_calls to pass to create_plan here as
+ * we've not yet planned the outer query!
+ */
+ plan = create_plan(subroot, best_path, 1.0);
/* And convert to SubPlan or InitPlan format. */
result = build_subplan(root, plan, subroot, plan_params,
@@ -284,7 +288,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
AlternativeSubPlan *asplan;
/* OK, finish planning the ANY subquery */
- plan = create_plan(subroot, best_path);
+ plan = create_plan(subroot, best_path, 1.0);
/* ... and convert to SubPlan format */
hashplan = castNode(SubPlan,
@@ -997,7 +1001,7 @@ SS_process_ctes(PlannerInfo *root)
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
best_path = final_rel->cheapest_total_path;
- plan = create_plan(subroot, best_path);
+ plan = create_plan(subroot, best_path, 1.0);
/*
* Make a SubPlan node for it. This is just enough unlike
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 57c02bff45..9c57896de9 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -38,7 +38,8 @@ typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
- Plan *outer_plan);
+ Plan *outer_plan,
+ double est_calls);
typedef void (*BeginForeignScan_function) (ForeignScanState *node,
int eflags);
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 244d1e1197..6e78b06f10 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -119,6 +119,8 @@ typedef struct PlannerGlobal
int lastPlanNodeId; /* highest plan node ID assigned */
+ int jitFlags; /* OR mask of jitFlags for each plan node */
+
bool transientPlan; /* redo plan when TransactionXmin changes? */
bool dependsOnRole; /* is plan specific to current role? */
@@ -1533,6 +1535,8 @@ typedef struct MemoizePath
uint32 est_entries; /* The maximum number of entries that the
* planner expects will fit in the cache, or 0
* if unknown */
+ double est_hitratio; /* An estimate on the ratio of how many calls
+ * will result in a cache hit. */
} MemoizePath;
/*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e43e360d9b..1ea4c78bcb 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -118,6 +118,9 @@ typedef struct Plan
Cost startup_cost; /* cost expended before fetching any tuples */
Cost total_cost; /* total cost (assuming all tuples fetched) */
+ double est_calls; /* estimated number of times this plan will be
+ * (re)scanned */
+
/*
* planner's estimate of result size of this plan step
*/
@@ -135,6 +138,11 @@ typedef struct Plan
*/
bool async_capable; /* engage asynchronous-capable logic? */
+ /*
+ * information needed for jit
+ */
+ bool jit; /* jit compile for this plan node? */
+
/*
* Common structural data for all Plan types.
*/
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index c4f61c1a09..4b554d2a98 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -38,13 +38,15 @@ extern void preprocess_minmax_aggregates(PlannerInfo *root);
/*
* prototypes for plan/createplan.c
*/
-extern Plan *create_plan(PlannerInfo *root, Path *best_path);
+extern Plan *create_plan(PlannerInfo *root, Path *best_path,
+ double est_calls);
extern ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
Index scanrelid, List *fdw_exprs, List *fdw_private,
List *fdw_scan_tlist, List *fdw_recheck_quals,
Plan *outer_plan);
extern Plan *change_plan_targetlist(Plan *subplan, List *tlist,
- bool tlist_parallel_safe);
+ bool tlist_parallel_safe,
+ double est_calls);
extern Plan *materialize_finished_plan(Plan *subplan);
extern bool is_projection_capable_path(Path *path);
extern bool is_projection_capable_plan(Plan *plan);
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
index 48620edbc2..416dedee9a 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -100,6 +100,7 @@ select explain_filter('explain (analyze, buffers, format xml) select * from int8
<Total-Cost>N.N</Total-Cost> +
<Plan-Rows>N</Plan-Rows> +
<Plan-Width>N</Plan-Width> +
+ <Plan-Calls>N</Plan-Calls> +
<Actual-Startup-Time>N.N</Actual-Startup-Time> +
<Actual-Total-Time>N.N</Actual-Total-Time> +
<Actual-Rows>N</Actual-Rows> +
@@ -148,6 +149,7 @@ select explain_filter('explain (analyze, buffers, format yaml) select * from int
Total Cost: N.N +
Plan Rows: N +
Plan Width: N +
+ Plan Calls: N +
Actual Startup Time: N.N +
Actual Total Time: N.N +
Actual Rows: N +
@@ -199,6 +201,7 @@ select explain_filter('explain (buffers, format json) select * from int8_tbl i8'
"Total Cost": N.N, +
"Plan Rows": N, +
"Plan Width": N, +
+ "Plan Calls": N, +
"Shared Hit Blocks": N, +
"Shared Read Blocks": N, +
"Shared Dirtied Blocks": N, +
@@ -244,6 +247,7 @@ select explain_filter('explain (analyze, buffers, format json) select * from int
"Total Cost": N.N, +
"Plan Rows": N, +
"Plan Width": N, +
+ "Plan Calls": N, +
"Actual Startup Time": N.N, +
"Actual Total Time": N.N, +
"Actual Rows": N, +
@@ -363,6 +367,7 @@ select jsonb_pretty(
"Schema": "public", +
"Node Type": "Seq Scan", +
"Plan Rows": 0, +
+ "Plan Calls": 0, +
"Plan Width": 0, +
"Total Cost": 0.0, +
"Actual Rows": 0, +
@@ -409,6 +414,7 @@ select jsonb_pretty(
], +
"Node Type": "Sort", +
"Plan Rows": 0, +
+ "Plan Calls": 0, +
"Plan Width": 0, +
"Total Cost": 0.0, +
"Actual Rows": 0, +
@@ -452,6 +458,7 @@ select jsonb_pretty(
], +
"Node Type": "Gather Merge", +
"Plan Rows": 0, +
+ "Plan Calls": 0, +
"Plan Width": 0, +
"Total Cost": 0.0, +
"Actual Rows": 0, +
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Making JIT more granular
2022-04-26 05:24 Re: Making JIT more granular David Rowley <[email protected]>
@ 2022-05-14 00:35 ` Andy Fan <[email protected]>
2022-05-16 00:06 ` Re: Making JIT more granular Andy Fan <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Andy Fan @ 2022-05-14 00:35 UTC (permalink / raw)
To: David Rowley <[email protected]>; +Cc: PostgreSQL Developers <[email protected]>; Andres Freund <[email protected]>; Magnus Hagander <[email protected]>
Hi David:
> Does anyone have any thoughts about this JIT costing? Is this an
> improvement? Is there a better way?
>
>
I think this is an improvement. However I'm not sure how much improvement
& effort we want pay for it. I just shared my thoughts to start this
discussion.
1. Ideally there is no GUC needed at all. For given a operation, like
Expression execution, tuple deform, if we can know the extra cost
of JIT in compile and the saved cost of JIT in execution, we
can choose JIT automatically. But as for now, it is hard to
say both. and we don't have a GUC to for DBA like jit_compile_cost
/ jit_compile_tuple_deform_cost as well. Looks we have some
long way to go for this and cost is always a headache.
2. You calculate the cost to compare with jit_above_cost as:
plan->total_cost * plan->est_loops.
An alternative way might be to consider the rescan cost like
cost_rescan. This should be closer for a final execution cost.
However since it is hard to set a reasonable jit_above_cost,
so I am feeling the current way is OK as well.
3. At implementation level, I think it would be terrible to add
another parameter like est_loops to every create_xxx_plan
in future, An alternative way may be:
typedef struct
{
int est_calls;
} ExtPlanInfo;
void
copyExtPlanInfo(Plan *targetPlan, ExtPlanInfo ext)
{
targetPlan->est_calls = ext.est_calls;
}
create_xxx_plan(..., ExtPlanInfo extinfo)
{
copyExtPlanInfo(plan, extinfo);
}
By this way, it would be easier to add another parameter
like est_calls easily. Not sure if this is over-engineered.
I have gone through the patches for a while, General it looks
good to me. If we have finalized the design, I can do a final
double check.
At last, I think the patched way should be better than
the current way.
--
Best Regards
Andy Fan
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Making JIT more granular
2022-04-26 05:24 Re: Making JIT more granular David Rowley <[email protected]>
2022-05-14 00:35 ` Re: Making JIT more granular Andy Fan <[email protected]>
@ 2022-05-16 00:06 ` Andy Fan <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Andy Fan @ 2022-05-16 00:06 UTC (permalink / raw)
To: David Rowley <[email protected]>; +Cc: PostgreSQL Developers <[email protected]>; Andres Freund <[email protected]>; Magnus Hagander <[email protected]>
>
>
> 2. You calculate the cost to compare with jit_above_cost as:
>
> plan->total_cost * plan->est_loops.
>
> An alternative way might be to consider the rescan cost like
> cost_rescan. This should be closer for a final execution cost.
> However since it is hard to set a reasonable jit_above_cost,
> so I am feeling the current way is OK as well.
>
There are two observers after thinking more about this. a). due to the
rescan cost reason, plan->total_cost * plan->est_loops might be greater
than the whole plan's total_cost. This may cause users to be confused why
this change can make the plan not JITed in the past, but JITed now.
explain analyze select * from t1, t2 where t1.a = t2.a;
QUERY PLAN
------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..154.25 rows=100 width=16) (actual
time=0.036..2.618 rows=100 loops=1) Join Filter: (t1.a = t2.a) Rows
Removed by Join Filter: 9900 -> Seq Scan on t1 (cost=0.00..2.00
rows=100 width=8) (actual time=0.015..0.031 rows=100 loops=1) ->
Materialize (cost=0.00..2.50 rows=100 width=8) (actual time=0.000..0.010
rows=100 loops=100) -> Seq Scan on t2 (cost=0.00..2.00 rows=100
width=8) (actual time=0.007..0.023 rows=100 loops=1) Planning Time: 0.299
ms Execution Time: 2.694 ms (8 rows)
The overall plan's total_cost is 154.25, but the Materialize's JIT cost is
2.5 * 100 = 250.
b). Since the total_cost for a plan counts all the costs for its children,
so if one
child plan is JITed, I think all its parents would JITed. Is this by
design?
QUERY PLAN
----------------------------
Sort
Sort Key: (count(*))
-> HashAggregate
Group Key: a
-> Seq Scan on t1
(If Seq Scan is JITed, both HashAggregate & Sort will be JITed.)
--
Best Regards
Andy Fan
^ permalink raw reply [nested|flat] 49+ messages in thread
end of thread, other threads:[~2022-05-16 00:06 UTC | newest]
Thread overview: 49+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH 03/10] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH 03/10] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v37 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v36 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2020-05-11 07:50 [PATCH v38 2/6] Introduce UniqueKey attributes on RelOptInfo struct. 一挃 <[email protected]>
2022-04-26 05:24 Re: Making JIT more granular David Rowley <[email protected]>
2022-05-14 00:35 ` Re: Making JIT more granular Andy Fan <[email protected]>
2022-05-16 00:06 ` Re: Making JIT more granular Andy Fan <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox