public inbox for [email protected]
help / color / mirror / Atom feedFrom: Tomas Vondra <[email protected]>
Subject: [PATCH 04/10] review
Date: Tue, 16 Mar 2021 19:26:40 +0100
---
src/backend/nodes/copyfuncs.c | 4 +
src/backend/nodes/makefuncs.c | 1 -
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 2 +
src/backend/optimizer/path/README.uniquekey | 285 +++---
src/backend/optimizer/path/allpaths.c | 10 +-
src/backend/optimizer/path/joinrels.c | 7 +
src/backend/optimizer/path/uniquekeys.c | 906 ++++++++++++++------
src/backend/optimizer/plan/planner.c | 10 +
src/backend/optimizer/prep/prepunion.c | 1 +
src/backend/optimizer/util/inherit.c | 1 +
11 files changed, 863 insertions(+), 365 deletions(-)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 75c1c5e824..9d832ddc03 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2296,6 +2296,9 @@ _copyPathKey(const PathKey *from)
return newnode;
}
+/*
+ * _copyUniqueKey
+ */
static UniqueKey *
_copyUniqueKey(const UniqueKey *from)
{
@@ -2306,6 +2309,7 @@ _copyUniqueKey(const UniqueKey *from)
return newnode;
}
+
/*
* _copyRestrictInfo
*/
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 40415d0f5b..e156c9cdf8 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -816,7 +816,6 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
return v;
}
-
/*
* makeUniqueKey
*/
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 44154cde6a..13905e6037 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2460,6 +2460,7 @@ static void
_outUniqueKey(StringInfo str, const UniqueKey *node)
{
WRITE_NODE_TYPE("UNIQUEKEY");
+
WRITE_NODE_FIELD(exprs);
WRITE_BOOL_FIELD(multi_nullvals);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b3e212bf1c..8830c8df99 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -496,8 +496,10 @@ static UniqueKey *
_readUniqueKey(void)
{
READ_LOCALS(UniqueKey);
+
READ_NODE_FIELD(exprs);
READ_BOOL_FIELD(multi_nullvals);
+
READ_DONE();
}
diff --git a/src/backend/optimizer/path/README.uniquekey b/src/backend/optimizer/path/README.uniquekey
index 5eac761995..31cdb5ed65 100644
--- a/src/backend/optimizer/path/README.uniquekey
+++ b/src/backend/optimizer/path/README.uniquekey
@@ -1,131 +1,208 @@
-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.
+review comments:
+XXX Maybe move this to src/backend/optimizer/README.uniquekey?
+XXX multi_nullvals name seems a bit weird
+XXX no info about populate_distinctrel_uniquekeys, populate_grouprel_uniquekeys, populate_unionrel_uniquekeys
+-----
+src/backend/optimizer/path/README.uniquekey
+
+UniqueKey
+=========
+
+UniqueKey is a set of exprs for a RelOptInfo, which are known to have unique
+values on all the rows in the relation. A trivial example is a primary key
+defined on a relation - each attributes of the constraint is a unique key.
+
+We can use this knowledge to perform optimization in a number of places. Some
+of the optimizations are fairly obvious, others are less so:
+
+1. remove DISTINCT node if the clause is unique
+2. remove aggregation if group by clause is unique
+3. remove_useless_joins
+4. reduce_semianti_joins
+5. Index Skip Scan (WIP)
+6. Aggregation Push-Down without 2-phase aggregation if the join can't
+ duplicate the aggregated rows. (WIP)
+
-However we define the UnqiueKey as below.
+UniqueKey struct
+----------------
-typedef struct UniqueKey
-{
+A UnqiueKey is represented by the following struct:
+
+ 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:
+ } UniqueKey;
+
+exprs is a list of exprs which are know to be unique on current RelOptInfo.
+
+exprs = NIL is a special case, meaning there is only one row in the relation.
+This has has a stronger semantic than others. Consider for example
+
+ SELECT uk FROM t
+
+where 'uk' is a unique key. This guarantees uniqueness, but there may be mamy
+rows in the relation. On the other hand, consider this query
+
+ SELECT colx FROM t WHERE uk = const
+
+In this case we know there's only a single matching row (thanks to a condition
+on the unique key), which in turn guarantees uniqueness of the colx value, even
+if there is no constraint on the column itself.
+
+This knowledge is used in innerrel_is_unique, and is handled as a special case
+in add_uniquekey_for_onerow.
+
+
+The multi_nullvals field tracks whether the expressions may contain multiple
+NULL values. This can happen for example when the unique key is derived from
+a unique index with nullable columns, or because of outer joins (which may add
+NULL values to a known-unique list - see populate_joinrel_uniquekeys).
+
+In this case uniqueness is not guaranteed, but we can still use the information
+in places places where NULL values are harmless - when removing useless joins,
+reducing semijoins, and so on.
+
+
+How is it maintained?
+---------------------
+
+Deducing the unique keys depends on the type of the relation - for each case
+there's a separate "populate" function:
+
+
+populate_baserel_uniquekeys
+---------------------------
+
+There are three cases, all assuming there's a unique index (e.g. 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)
+
+The last query is the "one row" case, in which case every column is Unique.
+
+
+populate_joinrel_uniquekeys
+---------------------------
+
+For joins, deducing the unique keys may be fairly complex and error-prone.
+We've simplified the rules like this:
+
+1. If the UniqueKey on an input relation can't be duplicated by the join, then
+it will be valid for the join rel. A typical example is a join like this:
+
+ inner_rel.any_col = outer_rel.unique_key
+
+The function used to detect this is innerrel_keeps_unique.
+
+2. Any combination of unique keys on each side of the join is a unique key
+for the join relation. This can be proved by contradiction - assume we have
+unique key on either side of the join - uk1 and uk2. If the values in uk1 get
+duplicated by the join with uk2 (by matching the row to multiple rows), the
+duplicated rows must have different values in the uk2.
+
+We can also leverage information about the "one row" case:
+
+1. If one of the input relations is known to have a single row, and the join
+can't duplicate the row (e.g. semi/anti join), we can keep the unique keys.
+It may however contain multi_nullvals after an outer join.
+
+XXX Not sure I understand the original logic/wording :-(
+
+2. If either UniqueKey can be duplicated after a join, there can be only one
+row only when both sides are "one row" AND there is no outer join.
+
+XXX Why the restriction on not allowing outer joins?
+
+3. Whenever the one row UniqueKey is not a valid any more, we need to convert
+UniqueKey to normal unique key since we don't store exprs for one-row relation.
+This is done by get_exprs_from_uniquekeys.
+
+The join case needs to be careful about multi_nullvals too:
+
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.
+mulit_nullvals in any case too.
+
+2. If the original unique key doesn't allow multi_nullvals, the unique key for
+the join relation may allow multi_nullvals after an outer join.
+
+
+subqueries
+----------
+
+It's necessary to "translate" unique keys between a subquery and the outer rels,
+which is what convert_subquery_uniquekeys does. This does almost exactly what
+convert_subquery_pathkeys does for pathkeys. It keeps only unique keys matching
+Vars in the outer relation. The relationship between outerrel.Var and
+subquery.exprs is built from outerel->subroot->processed_tlist.
-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.
+set-returning functions
+------------------------
+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 place.
-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.
+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 correct.
-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.
+populate_partitionedrel_uniquekeys
+----------------------------------
+
+As for inherit table, we first build the UnqiueKey on childrel as well. But for
+partitioned table we need to maintain two different kinds of UniqueKey:
+
+1) UniqueKey on the parent relation
+
+2) UniqueKey on child
+
+This is needed because a unique key from the partition may not be be unique key
+on the partitioned table.
+
Example:
-CREATE TABLE p (a int not null, b int not null) partition by list (a);
+
+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);
+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;
+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.
+Now "b" is only unique on partition level, but the two partitions may contain
+duplicate values for the "b" column (with different values in "a"). That means
+the DISTINCT clause can't be removed.
-Another usage of UniqueKey on partition level is it be helpful for
-partition-wise join.
+Now consider:
-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:
+SELECT DISTINCT a, b FROM p WHERE a = 1
+
+In this case, the optimizer eliminates all partitions except for one, so that
+the UniqueKey is valid for the parent relation too.
+
+UniqueKey at a partition level is useful for partition-wise join too.
+
+XXX Explain why is it useful?
+
+A UniqueKey from a partition can be transferred to the parent relation, in two
+cases. A trivial case is if there's a single child relation (e.g. thanks to
+partition elimination). In that case all unique keys on the child relation are
+automatically valid for the parent relation. If there are multiple relations,
+the unique key must be defived from an index present in all partitions, and the
+index has to include the partition key.
+
+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 66bf6f19f7..a801707eaa 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -581,7 +581,8 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/*
* Now that we've marked which partial indexes are suitable, we can now
- * build the relation's unique keys.
+ * build the relation's unique keys. We need to do it in this order,
+ * so that we don't deduce unique keys from inapplicable partial indexes.
*/
populate_baserel_uniquekeys(root, rel, rel->indexlist);
@@ -1305,6 +1306,12 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Add paths to the append relation. */
add_paths_to_append_rel(root, rel, live_childrels);
+
+ /*
+ * XXX Maybe move the check into populate populate_partitionedrel_uniquekeys?
+ * XXX What if it's append rel (but not partitioned one), but there's only one
+ * child relation? We could still deduce unique keys, no?
+ */
if (IS_PARTITIONED_REL(rel))
populate_partitionedrel_uniquekeys(root, rel, live_childrels);
}
@@ -2314,6 +2321,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
pathkeys, required_outer));
}
+ /* Convert subpath's unique keys to outer representation */
convert_subquery_uniquekeys(root, rel, sub_final_rel);
/* If outer rel allows parallelism, do same for partial paths. */
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 7271f044ec..eefba449d6 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -925,6 +925,13 @@ populate_joinrel_with_paths(PlannerInfo *root, RelOptInfo *rel1,
/* Apply partitionwise join technique, if possible. */
try_partitionwise_join(root, rel1, rel2, joinrel, sjinfo, restrictlist);
+ /*
+ * Determine which of the unique keys from input relations are applicable
+ * for the join result.
+ *
+ * XXX We do this after trying the partitionwise join, because that may allow
+ * using additional unique keys.
+ */
populate_joinrel_uniquekeys(root, joinrel, rel1, rel2, restrictlist, sjinfo->jointype);
}
diff --git a/src/backend/optimizer/path/uniquekeys.c b/src/backend/optimizer/path/uniquekeys.c
index 77ed2b2eff..114e8334f5 100644
--- a/src/backend/optimizer/path/uniquekeys.c
+++ b/src/backend/optimizer/path/uniquekeys.c
@@ -36,7 +36,7 @@ typedef struct UniqueKeyContextData
bool useful;
} *UniqueKeyContext;
-static List *initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel);
+static List *initialize_uniquecontext_for_joinrel(RelOptInfo *inputrel);
static bool innerrel_keeps_unique(PlannerInfo *root,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
@@ -80,8 +80,20 @@ static void add_uniquekey_from_sortgroups(PlannerInfo *root,
/*
* populate_baserel_uniquekeys
- * Populate 'baserel' uniquekeys list by looking at the rel's unique index
- * and baserestrictinfo
+ * Build list of unique keys for the base relation.
+ *
+ * Inspects unique indexes defined on the relation and determines what
+ * unique keys are valid. Partial indexes are considered too, if the
+ * predicate is valid.
+ *
+ * This also inspects baserestrictinfo, because we need to determine
+ * which opclass families are interesting when inspecting indexes. If we
+ * have a unique index and distinct clause with a mismatching opclasses,
+ * we should not use that.
+ *
+ * XXX Why does this look at baserestrictinfo?
+ *
+ * XXX What about collations?
*/
void
populate_baserel_uniquekeys(PlannerInfo *root,
@@ -99,22 +111,48 @@ populate_baserel_uniquekeys(PlannerInfo *root,
Assert(baserel->rtekind == RTE_RELATION);
+ if (!indexlist)
+ return;
+
+ /*
+ * Determine which unique indexes to use to build the unique keys.
+ * We have to skip partial with predicates not matched by the query,
+ * and unique indexes that are not immediately enforced.
+ *
+ * XXX Do we actually skip indexes that are not immediate?
+ * XXX What about hypothetical indexes?
+ */
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 there are not applicable unique indexes, we're done. */
if (matched_uniq_indexes == NIL)
return;
- /* Check which attrs is used in baserel->reltarget */
- pull_varattnos((Node *)baserel->reltarget->exprs, baserel->relid, &used_attrs);
+ /*
+ * Determine which attrs are referenced in baserel->reltarget. To use the
+ * unique key info, we need all the columns - a unique index on (a,b) may
+ * not be unique on (a). If a column is missing in reltarget, the nodes
+ * above can't possibly use it, and we can just ignore any matching index.
+ */
+ pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, &used_attrs);
- /* Check which attrno is used at a mergeable const filter */
+ /*
+ * Check which attrno is used at a mergeable const filter
+ *
+ * XXX This is not lookint att attrno at all, maybe obsolete comment?
+ *
+ * Seems the primary purpose of this is determining which opclass
+ * families to use when matching unique indexes in the next loop?
+ */
foreach(lc, baserel->baserestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
@@ -122,6 +160,10 @@ populate_baserel_uniquekeys(PlannerInfo *root,
if (rinfo->mergeopfamilies == NIL)
continue;
+ /*
+ * XXX What if bms_is_empty is true for both left_relids/right_relids?
+ * Or what if it's false in both cases?
+ */
if (bms_is_empty(rinfo->left_relids))
{
const_exprs = lappend(const_exprs, get_rightop(rinfo->clause));
@@ -136,40 +178,69 @@ populate_baserel_uniquekeys(PlannerInfo *root,
expr_opfamilies = lappend(expr_opfamilies, rinfo->mergeopfamilies);
}
+ /*
+ * Now try to match unique indexes to attributes in reltarget, and to
+ * merge operator families. The index may be on the right attributes,
+ * but if it's not matching the opfamily it's useless.
+ *
+ * XXX Can we have multiple baserestrictinfo for the same attribute,
+ * with different opfamilies? Probably not.
+ */
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)
+ bool multi_nullvals,
+ useful;
+
+ IndexOptInfo *index_info = (IndexOptInfo *) lfirst_node(IndexOptInfo, lc);
+
+ List *exprs = get_exprs_from_uniqueindex(index_info,
+ const_exprs,
+ expr_opfamilies,
+ used_attrs,
+ &useful,
+ &multi_nullvals);
+
+ if (!useful)
+ continue;
+
+ /*
+ * All the columns in Unique Index matched with a restrictinfo, so
+ * that we know there's just a one row in the result. If we find
+ * such index, we're done - we discard all other unique keys and
+ * keep just this special one. In principle, this is a stronger
+ * guarantee, because all subsets of one row are still unique.
+ *
+ * XXX Is it correct to just return? Doesn't that prevent some
+ * optimizations that might be possible with the other keys?
+ */
+ if (exprs == NIL)
{
- 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));
+ /* discards all previous uniquekeys */
+ 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.
+ * Determine unique keys for a partitioned relation.
+ *
+ * Inspects unique keys for all partitions and derives unique keys that
+ * are valid for the whole partitioned table. There are two basic cases:
+ *
+ * 1) There's only one remaining partition (thanks to pruning all other
+ * partitions). In this case all the unique keys from the partition are
+ * trivially valid for the partitioned table.
+ *
+ * 2) All the partitions have the same unique index (on the same set of
+ * columns), and the index includes the partition key. This ensures the
+ * combination of values is unique for the whole partitioned table.
*/
-
void
populate_partitionedrel_uniquekeys(PlannerInfo *root,
RelOptInfo *rel,
@@ -180,110 +251,181 @@ populate_partitionedrel_uniquekeys(PlannerInfo *root,
RelOptInfo *childrel;
bool is_first = true;
+ /* XXX What about append rels? At least for the one-child case? */
Assert(IS_PARTITIONED_REL(rel));
+ /* if there are no child relations, we're done. */
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 there is only one partition used in this query, the UniqueKey for
+ * a child relation is still valid for the parent level. We need to
+ * 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 the partition has a single row, so does the parent. */
if (relation_is_onerow(childrel))
{
add_uniquekey_for_onerow(rel);
return;
}
+ /*
+ * Inspect the unique keys one by one, try reusing them for the
+ * parent relation.
+ *
+ * FIXME This needs more work to handle expressions and not just
+ * simple Vars.
+ */
foreach(lc, childrel->uniquekeys)
{
+ ListCell *lc2;
+ List *parent_exprs = NIL;
+ bool can_reuse = true;
+
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;
+
+ /*
+ * XXX Not sure what exactly we do here. Surely we deal with
+ * expressions at child/parent level elsewhere? Can't we just
+ * copy the code from there?
+ */
foreach(lc2, ukey->exprs)
{
- Var *var = (Var *)lfirst(lc2);
+ 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.
+ * XXX For now this only supports simple Var expressions,
+ * so if there's a more complex expression we'll not copy
+ * the unique key to the parent.
*/
if(!IsA(var, Var))
{
can_reuse = false;
break;
}
+
/* Convert it to parent var */
- parent_exprs = lappend(parent_exprs, find_parent_var(appinfo, 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));
+
+ /* ignore unique keys with complex expressions */
+ if (!can_reuse)
+ continue;
+
+ rel->uniquekeys = lappend(rel->uniquekeys,
+ makeUniqueKey(parent_exprs,
+ ukey->multi_nullvals));
}
+
+ return;
}
- else
+
+ /*
+ * A parent with multiple child relations. We only care about indexes that
+ * are in all child relations, so we loop through indexes on the first one
+ * and check that they exist in the other child relations too.
+ */
+
+ childrel = linitial_node(RelOptInfo, childrels);
+ foreach(lc, childrel->indexlist)
{
- /* 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;
+ IndexOptInfo *ind = lfirst(lc);
+ IndexOptInfo *modified_index;
- /*
- * 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;
+ /*
+ * Ignore indexes that are not unique, immediately enforced. Partial
+ * indexes with mismatched predicate are useless too.
+ */
+ if (!ind->unique || !ind->immediate ||
+ (ind->indpred != NIL && !ind->predOK))
+ 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;
+ /*
+ * 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.
+ *
+ * FIXME We should support indexes on expressions.
+ */
+ if (ind->indexprs != NIL)
+ continue;
- global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
- }
+ /*
+ * Adopt the index definition for the parent.
+ *
+ * XXX This seems rather weird. We're constructing "artificial" index
+ * for the partitioned table (kinda like a global index). Can't we
+ * just have some simpler struct representing it?
+ */
+ 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.
+ *
+ * XXX Can't we do this check before simple_copy_indexinfo_to_parent?
+ */
+ if (!index_constains_partkey(rel, modified_index))
+ continue;
- if (global_uniq_indexlist != NIL)
+ global_uniq_indexlist = lappend(global_uniq_indexlist, modified_index);
+ }
+
+ /* if there are no applicable unique indexes, we're done */
+ if (!global_uniq_indexlist)
+ return;
+
+ /*
+ * We iterate over the child relations first, and inspect the unique
+ * indexes for each hild, because this way we can stop early if we
+ * happen to eliminate all the unique indexes.
+ */
+ foreach(lc, childrels)
+ {
+ RelOptInfo *child = lfirst(lc);
+
+ /* skip the first index, which is where we got the list from */
+ if (is_first)
{
- 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);
+ is_first = false;
+ continue;
}
+
+ /* match the unique keys to indexes on this child */
+ adjust_partition_unique_indexlist(root, rel, child, &global_uniq_indexlist);
+
+ /*
+ * If we have eliminated all unique indexes, no point in looking at
+ * the remaining child relations.
+ */
+ if (!global_uniq_indexlist)
+ break;
}
+
+ /* Now we have a list of unique index which are exactly same on all child
+ * relations. Set the UniqueKey just like it is non-partition table.
+ */
+ populate_baserel_uniquekeys(root, rel, global_uniq_indexlist);
}
/*
* populate_distinctrel_uniquekeys
+ * Update unique keys for relation produced by DISTINCT.
+ *
+ * We can keep all unique keys from the input relations, because DISTINCT
+ * can only remove rows - it can't duplicate them. Also, the DISTINCT clause
+ * itself is a unique key, so add that.
*/
void
populate_distinctrel_uniquekeys(PlannerInfo *root,
@@ -292,11 +434,13 @@ populate_distinctrel_uniquekeys(PlannerInfo *root,
{
/* 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,
@@ -305,54 +449,76 @@ populate_grouprel_uniquekeys(PlannerInfo *root,
{
Query *parse = root->parse;
- bool input_ukey_added = false;
ListCell *lc;
+ /*
+ * XXX Is this actually valid, before checking fro grouping sets?
+ * The grouping sets may produce duplicate row even with just a single
+ * input row, I think.
+ */
if (relation_is_onerow(inputrel))
{
add_uniquekey_for_onerow(grouprel);
return;
}
+
+ /*
+ * Bail out if there are grouping sets.
+ *
+ * XXX Could we maybe inspect the grouping sets and determine if this
+ * generates distinct combinations? In some cases that's clearly not
+ * the case (rollup, cube), but for some simple cases it might.
+ */
if (parse->groupingSets)
return;
- /* A Normal group by without grouping set. */
- if (parse->groupClause)
+ /* It has aggregation but without a group by, so only one row returned */
+ if (!parse->groupClause)
+ add_uniquekey_for_onerow(grouprel);
+
+ /*
+ * A regular group by, without grouping sets.
+ *
+ * Obviously, the whole group clause determines a unique key. But if
+ * there are smaller unique keys on the input rel, we prefer those
+ * because those are more flexible. If (a,b) is unique, (a,b,c) is
+ * unique too. Only when there are no such smaller unique keys, we
+ * add the unique key derived from the group clause.
+ */
+ foreach(lc, inputrel->uniquekeys)
{
+ UniqueKey *ukey = lfirst_node(UniqueKey, lc);
+
/*
- * 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.
+ * Ignore unique keys on the input that are not subset of the
+ * group clause. We can't use incomplete unique keys.
*/
- 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);
+ if (!list_is_subset(ukey->exprs, grouprel->reltarget->exprs))
+ continue;
+
+ grouprel->uniquekeys = lappend(grouprel->uniquekeys, ukey);
}
- else
- /* It has aggregation but without a group by, so only one row returned */
- add_uniquekey_for_onerow(grouprel);
+
+ /*
+ * Group clause must be a super-set of of grouprel->reltarget->exprs,
+ * except for the aggregation expressions. So if we found a smaller
+ * unique key on the input relation, don't bother adding a unique key
+ * for the group clause.
+ */
+ if (!grouprel->uniquekeys)
+ add_uniquekey_from_sortgroups(root,
+ grouprel,
+ root->parse->groupClause);
}
/*
* simple_copy_uniquekeys
- * Using a function for the one-line code makes us easy to check where we simply
- * copied the uniquekey.
+ * Copy yhe unique keys between relations.
+ *
+ * Using a function for the one-line code makes us easy to check where we
+ * simply copied the uniquekey.
+ *
+ * XXX Seems like an overkill, not sure what's the purpose?
*/
void
simple_copy_uniquekeys(RelOptInfo *oldrel,
@@ -362,24 +528,27 @@ simple_copy_uniquekeys(RelOptInfo *oldrel,
}
/*
- * populate_unionrel_uniquekeys
+ * populate_unionrel_uniquekeys
+ * Determine unique keys for UNION relation.
+ *
+ * XXX Does this need to care about UNION vs. UNION ALL? At least in the
+ * one-row code path?
*/
void
populate_unionrel_uniquekeys(PlannerInfo *root,
- RelOptInfo *unionrel)
+ RelOptInfo *unionrel)
{
- ListCell *lc;
- List *exprs = NIL;
+ ListCell *lc;
+ List *exprs = NIL;
Assert(unionrel->uniquekeys == NIL);
+ /* XXX Why are we copying the expressions? */
foreach(lc, unionrel->reltarget->exprs)
- {
exprs = lappend(exprs, lfirst(lc));
- }
+ /* SQL: select union select; is valid, we need to handle it here. */
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,
@@ -389,6 +558,7 @@ populate_unionrel_uniquekeys(PlannerInfo *root,
/*
* populate_joinrel_uniquekeys
+ * Determine unique keys for a join relation.
*
* 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
@@ -404,70 +574,99 @@ 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 */
+ ListCell *lc,
+ *lc2;
+ List *clause_list = NIL;
+ List *outerrel_ukey_ctx;
+ List *innerrel_ukey_ctx;
+ bool inner_onerow,
+ outer_onerow;
+ bool mergejoin_allowed;
+
+ /* For SEMI/ANTI join, we care only about the outerrel unique keys. */
if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
{
foreach(lc, outerrel->uniquekeys)
{
UniqueKey *uniquekey = lfirst_node(UniqueKey, lc);
+
+ /* Keep the unique key if it's included in the joinrel. */
if (list_is_subset(uniquekey->exprs, joinrel->reltarget->exprs))
joinrel->uniquekeys = lappend(joinrel->uniquekeys, uniquekey);
}
+
return;
}
+ /* XXX What about JOIN_RIGHT? */
Assert(jointype == JOIN_LEFT || jointype == JOIN_FULL || jointype == JOIN_INNER);
- /* Fast path */
+ /*
+ * For regular joins, we need to combine unique keys from both sides
+ * of the join, to get a new unique key for the join relation. So if
+ * either side does not have a unique key, bail out.
+ */
if (innerrel->uniquekeys == NIL || outerrel->uniquekeys == NIL)
return;
+ /* XXX maybe move to the if blocks? Not needed outside. */
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);
+ outerrel_ukey_ctx = initialize_uniquecontext_for_joinrel(outerrel);
+ innerrel_ukey_ctx = initialize_uniquecontext_for_joinrel(innerrel);
- clause_list = select_mergejoin_clauses(root, joinrel, outerrel, 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 */))
+ /*
+ * XXX Seems a bit weird that it's called innerrel_keeps_unique but we
+ * seem to use it in both directions. Or what's the "reverse" for? The
+ * "reverse" name is not particularly descriptive.
+ */
+ if (innerrel_keeps_unique(root, innerrel, outerrel, clause_list, true))
{
- bool outer_impact = jointype == JOIN_FULL;
+ bool outer_impact = (jointype == JOIN_FULL);
+
+ /* Inspect unique keys on the outer relation. */
foreach(lc, outerrel_ukey_ctx)
{
UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+ /*
+ * If the output of the join does not include all the parts of the
+ * unique key, it's useless, so mark it accordingly and ignore it.
+ */
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.
+ /*
+ * When the outer relation has one row, and the unique key is not
+ * duplicated after join, so the joinrel will still have just one
+ * row unless the jointype == JOIN_FULL. In that case we're done,
+ * it's the strictest unique key possible.
+ *
+ * If it's one-row with a JOIN_FULL, it might produce multiple
+ * rows with NULLs, so set multi_nullvals. We also need to set
+ * the exprs correctly since it can't be NIL any more.
+ *
+ * For other cases (not one-row relation), we just reuse the
+ * unique key, but we may need to tweak the multi_nullvals.
*/
if (outer_onerow && !outer_impact)
{
add_uniquekey_for_onerow(joinrel);
return;
}
- else if (outer_onerow)
+ else if (outer_onerow) /* one-row and FULL join */
{
- /*
- * 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,
@@ -485,18 +684,38 @@ populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
joinrel->uniquekeys = lappend(joinrel->uniquekeys,
ctx->uniquekey);
}
+
+ /*
+ * Mark the unique key as added, so that we can ignore it later
+ * when combining unique keys from both sides of the join.
+ */
ctx->added_to_joinrel = true;
}
}
+ /*
+ * XXX Seems this actually checks if "outerrel keeps unique" so the name
+ * is misleading. Of maybe it's the previous block, not sure.
+ *
+ * XXX So why does this consider JOIN_FULL and JOIN_LEFT, while the previous
+ * block only cares about JOIN_FULL?
+ *
+ * XXX This is almost exact copy of the previous block, so maybe make it
+ * a separate function and just call it twice?
+ */
if (innerrel_keeps_unique(root, outerrel, innerrel, clause_list, false))
{
- bool outer_impact = jointype == JOIN_FULL || jointype == JOIN_LEFT;;
+ bool outer_impact = (jointype == JOIN_FULL || jointype == JOIN_LEFT);
+ /* Inspect unique keys on the inner relation. */
foreach(lc, innerrel_ukey_ctx)
{
UniqueKeyContext ctx = (UniqueKeyContext)lfirst(lc);
+ /*
+ * If the output of the join does not include all the parts of the
+ * unique key, it's useless, so mark it accordingly and ignore it.
+ */
if (!list_is_subset(ctx->uniquekey->exprs, joinrel->reltarget->exprs))
{
ctx->useful = false;
@@ -529,29 +748,52 @@ populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
ctx->uniquekey);
}
+
+ /*
+ * Mark the unique key as added, so that we can ignore it later
+ * when combining unique keys from both sides of the join.
+ */
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.
+ * XXX What if either of the previous two conditions did not match? In
+ * that case we haven't updated the useful flag, and maybe the unique
+ * key is not useful, but we don't know, right? So we should not be
+ * using it in the next loop. Or maybe we should evaluate the flag
+ * before the loops.
+ */
+
+ /*
+ * The combination of the UniqueKey from both sides is unique as well,
+ * regardless of the join type. But don't bother to add it if its
+ * subset has been added to joinrel already or when it's not useful for
+ * the joinrel.
+ *
+ * XXX Maybe we should have a flag that both sides have useful keys?
+ * Or maybe the loops are short/cheap?
*/
foreach(lc, outerrel_ukey_ctx)
{
UniqueKeyContext ctx1 = (UniqueKeyContext) lfirst(lc);
+
+ /* when not useful or already added to the joinrel, skip it */
if (ctx1->added_to_joinrel || !ctx1->useful)
continue;
+
foreach(lc2, innerrel_ukey_ctx)
{
UniqueKeyContext ctx2 = (UniqueKeyContext) lfirst(lc2);
+
+ /* when not useful or already added to the joinrel, skip it */
if (ctx2->added_to_joinrel || !ctx2->useful)
continue;
+
+ /* If we add a onerow UniqueKey, we don't need another key. */
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;
}
}
@@ -560,8 +802,9 @@ populate_joinrel_uniquekeys(PlannerInfo *root, RelOptInfo *joinrel,
/*
* convert_subquery_uniquekeys
+ * Covert the UniqueKey in subquery to outer relation.
*
- * Covert the UniqueKey in subquery to outer relation.
+ * XXX Explain what exactly does the conversion do?
*/
void convert_subquery_uniquekeys(PlannerInfo *root,
RelOptInfo *currel,
@@ -618,12 +861,14 @@ void convert_subquery_uniquekeys(PlannerInfo *root,
/*
* innerrel_keeps_unique
+ * Check if Unique key on the innerrel is valid after join.
*
- * 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.
+ * 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.
+ *
+ * XXX Misleading name? We seem to use it for "outerrel_keeps_unique" too.
*/
static bool
innerrel_keeps_unique(PlannerInfo *root,
@@ -634,26 +879,32 @@ innerrel_keeps_unique(PlannerInfo *root,
{
ListCell *lc, *lc2, *lc3;
+ /* XXX probably not needed, duplicate with the check in the caller
+ * (populate_joinrel_uniquekeys). But it's cheap. */
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;
+ 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;
@@ -677,22 +928,37 @@ innerrel_keeps_unique(PlannerInfo *root,
/*
* relation_is_onerow
- * Check if it is a one-row relation by checking UniqueKey.
+ * Check if it is a one-row relation by checking UniqueKey.
+ *
+ * The one-row is a special case - there has to be just a single unique key,
+ * with no expressions.
*/
bool
relation_is_onerow(RelOptInfo *rel)
{
UniqueKey *ukey;
- if (rel->uniquekeys == NIL)
+
+ /* there has to be exactly one unique key */
+ if (list_length(rel->uniquekeys) != 1)
return false;
+
ukey = linitial_node(UniqueKey, rel->uniquekeys);
- return ukey->exprs == NIL && list_length(rel->uniquekeys) == 1;
+
+ /* the unique key must have no expressions */
+ return (ukey->exprs == NIL);
}
/*
* 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.
+ * Determines if the relation has unique key for a list of expressions.
+ *
+ * Returns true iff we can prove that the relation cannot return multiple rows
+ * with the same values in the provided expression.
+ *
+ * allow_multinulls determines whether we allow multiple NULL values or not.
+ *
+ * The special "one-row" unique key is considered incompatible with all
+ * possible expressions.
*/
bool
relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
@@ -710,20 +976,39 @@ relation_has_uniquekeys_for(PlannerInfo *root, RelOptInfo *rel,
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 expressions from a unique index.
+ *
+ * Provided with a list of expressions and opclass families, we try to match
+ * it to the index. If useful, we produce a list of index expressions (subset
+ * of the list we provided).
+ *
+ * We simply walk through the index expressions, and for each expression we
+ * check three things:
*
- * Return a list of exprs which is unique. set useful to false if this
- * unique index is not useful for us.
+ * 1) If there's a matching (expr = Const) clause, we can simply ignore the
+ * expressions. Unique index on (a,b,c) guarantees uniqueness on (a,b) when
+ * there's condition (c=1).
+ *
+ * 2) Check that the index expression is present in the relation we're
+ * dealing with. If not, the unique key would be useless anyway, and the
+ * index can't produce unique key.
+ *
+ * XXX Shouldn't it be enough to return NULL when the index is not useful?
+ * The extra flag seems a bit unnecessary.
*/
static List *
get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
@@ -743,18 +1028,19 @@ get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
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;
+ int attr = unique_index->indexkeys[c];
+ Expr *expr;
+ bool matched_const = false;
+ ListCell *lc1, *lc2;
- if(attr > 0)
+ if (attr > 0)
{
+ /* regular attribute, just use the expression from index tlist */
expr = list_nth_node(TargetEntry, unique_index->indextlist, c)->expr;
}
else if (attr == 0)
{
- /* Expression index */
+ /* expression from the index */
expr = lfirst(indexpr_item);
indexpr_item = lnext(unique_index->indexprs, indexpr_item);
}
@@ -764,29 +1050,43 @@ get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
Assert(false);
}
+ /* should have a valid expression now */
+ Assert(expr);
+
/*
- * Check index_col = Const case with regarding to opfamily checking
- * If we can remove the index_col from the final UniqueKey->exprs.
+ * Check if there's (index_col = Const) condition, and that it's using
+ * a compatible opfamily. If yes, we can remove the index_col from the
+ * final UniqueKey->exprs, because the value is constant (so removing
+ * it can't introduce duplicities).
*/
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))
+ List *opfamilies = (List *) lfirst(lc2);
+ Node *cexpr = (Node *) lfirst(lc1);
+
+ if (list_member_oid(opfamilies, unique_index->opfamily[c]) &&
+ match_index_to_operand(cexpr, c, unique_index))
{
matched_const = true;
break;
}
}
+ /* it's constant, so ignore the expression */
if (matched_const)
continue;
- /* Check if the indexed expr is used in rel */
+ /*
+ * Check if the indexed expr is used in rel. We do this after the
+ * (col = Const) check, because nn expression may be in a a restrict
+ * clause and not in the reltarget. So we don't want to rule out an
+ * index unnecessarily.
+ */
if (attr > 0)
{
/*
- * Normal Indexed column, if the col is not used, then the index is useless
- * for uniquekey.
+ * Normal indexed column, if the col is not used, then the index
+ * is useless for uniquekey.
*/
attr -= FirstLowInvalidHeapAttributeNumber;
@@ -806,67 +1106,85 @@ get_exprs_from_uniqueindex(IndexOptInfo *unique_index,
/* check not null property. */
if (attr == 0)
{
- /* We never know if a expression yields null or not */
+ /* We never know if an 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))
+ 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.
+ * Create a special unique key signifying that the rel has one row.
+ *
+ * If we are sure that the relation only returns one row (it might return
+ * no rows, but we still consider that unique), then all the columns are
+ * trivially unique.
+ *
+ * However we don't need to create UniqueKey with every column, we just
+ * set exprs = NIL, because that's easier to identify. We don't want to
+ * add unnecessary unique keys (such that we already have a unique key
+ * for a subset of the expressions), and with (exprs == NIL) we can just
+ * assume we have one unique key for each column in the rel.
+ *
+ * We discard all other unique keys, since it has the strongest semantics.
*/
void
add_uniquekey_for_onerow(RelOptInfo *rel)
{
/*
- * We overwrite the previous UniqueKey on purpose since this one has the
- * strongest semantic.
+ * We overwrite the previous UniqueKey on purpose since this one has
+ * the strongest semantic (all other unique keys are implied by it).
*/
rel->uniquekeys = list_make1(makeUniqueKey(NIL, false));
}
/*
- * initililze_uniquecontext_for_joinrel
- * Return a List of UniqueKeyContext for an inputrel
+ * initialize_uniquecontext_for_joinrel
+ * Return a List of UniqueKeyContext for an inputrel.
*/
static List *
-initililze_uniquecontext_for_joinrel(RelOptInfo *inputrel)
+initialize_uniquecontext_for_joinrel(RelOptInfo *inputrel)
{
- List *res = NIL;
- ListCell *lc;
- foreach(lc, inputrel->uniquekeys)
+ 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.
+ * Extract expressions that are part of a unique key.
+ *
+ * The meaning of the result is a bit different in regular and one-row cases.
+ * For the regular case, the list of expressions form a single unique key,
+ * i.e. the combination of values is unique.
+ *
+ * For the one-row case, each individual expression is known to be unique
+ * (simply because in a single row everything is unique).
*
* rel1: The relation which you want to get the exprs.
* ukey: The UniqueKey you want to get the exprs.
@@ -875,27 +1193,29 @@ static List *
get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *rel1, UniqueKey *ukey)
{
- ListCell *lc;
- bool onerow = rel1 != NULL && relation_is_onerow(rel1);
+ ListCell *lc;
+ List *res = NIL;
+ bool onerow = (rel1 != NULL) && relation_is_onerow(rel1);
- List *res = NIL;
+ /* We require at least one of those to be true. */
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
+
+ /* if not a one-row unique key, just return the key's expressions */
+ if (!onerow)
+ return list_make1(ukey->exprs);
+
+ /*
+ * If it's a one-row relation, we simply extract the expressions that
+ * still exist in the reltarget.
+ */
+ foreach(lc, joinrel->reltarget->exprs)
{
- res = list_make1(ukey->exprs);
+ Bitmapset *relids = pull_varnos(root, lfirst(lc));
+
+ if (bms_is_subset(relids, rel1->relids))
+ res = lappend(res, list_make1(lfirst(lc)));
}
+
return res;
}
@@ -910,55 +1230,67 @@ get_exprs_from_uniquekey(PlannerInfo *root, RelOptInfo *joinrel,
/*
* index_constains_partkey
- * return true if the index contains the partiton key.
+ * Determines if the index includes a partition key.
+ *
+ * XXX Surely we already have a code doing this already? E.g. when creating
+ * a unique index on a partitioned table we define that.
*/
static bool
-index_constains_partkey(RelOptInfo *partrel, IndexOptInfo *ind)
+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;
+ Node *part_expr = linitial(partrel->partexprs[i]);
+ bool found_in_index = false;
+
foreach(lc, ind->indextlist)
{
- Expr *index_expr = lfirst_node(TargetEntry, lc)->expr;
+ 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
+ * Compare two indexes to determine if they are the same.
+ *
+ * We need to do this because simple_copy_indexinfo_to_parent does change
+ * some elements. So this is not exactly the same as calling 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)
+ * XXX I wonder if we could simply use equal(), somehow? In fact, we should
+ * probably build something much simpler than IndexOptInfo, just enough to
+ * do the checks.
*/
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));
+ 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))));
}
@@ -981,11 +1313,21 @@ simple_indexinfo_equal(IndexOptInfo *ind1, IndexOptInfo *ind2)
/*
- * 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.
+ * simple_copy_indexinfo_to_parent
+ * Copy index info from child to parent, with necessary tweaks.
+ *
+ * We use this copy to check:
+ *
+ * 1. If the same/matching index exists in all the childrels.
* 2. If the parentrel->reltarget/basicrestrict info matches this index.
+ *
+ * XXX IMHO we should probably build something much simpler than a full
+ * IndexOptInfo copy, just enough to do the checks.
+ *
+ * XXX The fact that we copy so much data seems wrong, and having to
+ * define macros from copyfuncs.c seems like a very suspicious thing.
+ * One reason is that IndeOptInfo is fairly large struct, especially
+ * with all the fields, and we allocate it very often.
*/
static IndexOptInfo *
simple_copy_indexinfo_to_parent(PlannerInfo *root,
@@ -1027,20 +1369,24 @@ simple_copy_indexinfo_to_parent(PlannerInfo *root,
/*
* adjust_partition_unique_indexlist
+ * Checks and eliminates indexes that do not exist on the child relation.
*
- * 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.
+ * Walks the list of unique indexes, and eliminates those that don't match
+ * the child relation (i.e. where a matching child index does not exist).
+ * This is used to iteratively filter the list of candidate unique keys.
+ *
+ * After processing all child relations, the list contains only indexes that
+ * exist in all the child relations.
*/
static void
adjust_partition_unique_indexlist(PlannerInfo *root,
RelOptInfo *parentrel,
RelOptInfo *childrel,
- List **global_unique_indexes)
+ List **indexes)
{
ListCell *lc, *lc2;
- foreach(lc, *global_unique_indexes)
+
+ foreach(lc, *indexes)
{
IndexOptInfo *g_ind = lfirst_node(IndexOptInfo, lc);
bool found_in_child = false;
@@ -1049,23 +1395,45 @@ adjust_partition_unique_indexlist(PlannerInfo *root,
{
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))
+
+ /*
+ * Ignore child indexes that can't possibly match (not unique or
+ * immediate, etc.)
+ *
+ * XXX We do these checks in many places, so maybe turn it into
+ * a reusable macro?
+ */
+ if ((!p_ind->unique) || (!p_ind->immediate) ||
+ (p_ind->indpred != NIL) && (!p_ind->predOK))
continue;
+
+ /*
+ * XXX This seems possibly quite expensive. Imagine there are many
+ * child relations, with a bunch of unique indexes each. Then this
+ * generates a copy for each unique index in each child relation,
+ * something like O(N^2/2) copies.
+ */
p_ind_copy = simple_copy_indexinfo_to_parent(root, parentrel, p_ind);
+
+ /* Found a matching index for the child relation, we're done. */
if (simple_indexinfo_equal(p_ind_copy, g_ind))
{
found_in_child = true;
break;
}
}
+
+ /* No matching index in the child, so remove it from the list. */
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);
+ *indexes = foreach_delete_current(*indexes, lc);
}
}
-/* Helper function for groupres/distinctrel */
+/*
+ * Helper function for groupres/distinctrel
+ *
+ * FIXME Not sure about this.
+ */
static void
add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgroups)
{
@@ -1073,27 +1441,32 @@ add_uniquekey_from_sortgroups(PlannerInfo *root, RelOptInfo *rel, List *sortgrou
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.
+ * XXX: If there are some vars which are not in the 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;
+ /* sortgroupclause can't be multi_nullvals */
exprs = get_sortgrouplist_exprs(sortgroups, parse->targetList);
rel->uniquekeys = lappend(rel->uniquekeys,
- makeUniqueKey(exprs,
- false /* sortgroupclause can't be multi_nullvals */));
+ makeUniqueKey(exprs, false));
}
/*
* add_combined_uniquekey
- * The combination of both UniqueKeys is a valid UniqueKey for joinrel no matter
- * the jointype.
+ * Add a unique key for a join, combined from keys on inner/outer side.
+ *
+ * The combination of both UniqueKeys is a valid UniqueKey for joinrel no
+ * matter what's the exact jointype.
+ *
+ * Returns true if the unique key is "one-row" variant, so that the caller
+ * can stop considering further combinations.
*/
bool
add_combined_uniquekey(PlannerInfo *root,
@@ -1104,32 +1477,47 @@ add_combined_uniquekey(PlannerInfo *root,
UniqueKey *inner_ukey,
JoinType jointype)
{
+ bool multi_nullvals;
+ ListCell *lc1, *lc2;
- 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 ||
+ /*
+ * If either side has multi_nullvals, or we are dealing with an outer join,
+ * the combined UniqueKey has multi_nullvals too.
+ */
+ 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)
+ if (relation_is_onerow(outer_rel) &&
+ relation_is_onerow(inner_rel) &&
+ jointype == JOIN_INNER)
{
add_uniquekey_for_onerow(joinrel);
return true;
}
+ /*
+ * XXX Isn't this wrong? Why is it combining expressions that are part
+ * of the two unique keys? Imagine we have outer unique key on (a1, a2)
+ * and inner outer key on (b1, b2). Then this adds four unique keys
+ * for the join (a1,b1), (a1,b2), (a2,b1) and (a2,b2). Shouldn't it
+ * just add (a1,a2,b1,b2)?
+ */
foreach(lc1, get_exprs_from_uniquekey(root, joinrel, outer_rel, outer_ukey))
{
+ /*
+ * XXX This calls get_exprs_from_uniquekey repeatedly for each outer
+ * loop. Maybe we should calculate it just once before the loop.
+ */
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 8d8e493f5c..f29b65c07b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2387,6 +2387,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
add_path(final_rel, path);
}
+ /* XXX comment? can we simply just copy the unique keys to the final relation? */
simple_copy_uniquekeys(current_rel, final_rel);
/*
@@ -3902,7 +3903,9 @@ create_grouping_paths(PlannerInfo *root,
set_cheapest(grouped_rel);
+ /* XXX does this apply to grouping sets too? */
populate_grouprel_uniquekeys(root, grouped_rel, input_rel);
+
return grouped_rel;
}
@@ -4625,7 +4628,10 @@ create_window_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(window_rel);
+
+ /* XXX comment? */
simple_copy_uniquekeys(input_rel, window_rel);
+
return window_rel;
}
@@ -4939,7 +4945,10 @@ create_distinct_paths(PlannerInfo *root,
/* Now choose the best path(s) */
set_cheapest(distinct_rel);
+
+ /* XXX comment */
populate_distinctrel_uniquekeys(root, input_rel, distinct_rel);
+
return distinct_rel;
}
@@ -5200,6 +5209,7 @@ create_ordered_paths(PlannerInfo *root,
*/
Assert(ordered_rel->pathlist != NIL);
+ /* XXX comment */
simple_copy_uniquekeys(input_rel, ordered_rel);
return ordered_rel;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b7626545bf..72a3f3c598 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -691,6 +691,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
/* Add the UniqueKeys */
populate_unionrel_uniquekeys(root, result_rel);
+
return result_rel;
}
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3eec1f4d74..c9829c5fc4 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -755,6 +755,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
pseudoconstant,
rinfo->security_level,
NULL, NULL, NULL);
+ /* XXX This is a bit weird, doing this outside make_restrictinfo */
child_rinfo->mergeopfamilies = rinfo->mergeopfamilies;
childquals = lappend(childquals, child_rinfo);
/* track minimum security level among child quals */
--
2.30.2
--------------F495F6B18672582269F00FF9
Content-Type: text/x-patch; charset=UTF-8;
name="0005-Extend-UniqueKeys-20210317.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="0005-Extend-UniqueKeys-20210317.patch"
view thread (53+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: [PATCH 04/10] review
In-Reply-To: <no-message-id-1880631@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox