aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r--src/backend/optimizer/plan/createplan.c26
-rw-r--r--src/backend/optimizer/plan/planner.c27
-rw-r--r--src/backend/optimizer/plan/setrefs.c52
-rw-r--r--src/backend/optimizer/plan/subselect.c4
-rw-r--r--src/backend/optimizer/prep/prepjointree.c6
-rw-r--r--src/backend/optimizer/prep/preptlist.c13
-rw-r--r--src/backend/optimizer/util/plancat.c352
7 files changed, 470 insertions, 10 deletions
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index eeb2a417643..3246332d6e3 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4868,7 +4868,7 @@ make_modifytable(PlannerInfo *root,
Index nominalRelation,
List *resultRelations, List *subplans,
List *withCheckOptionLists, List *returningLists,
- List *rowMarks, int epqParam)
+ List *rowMarks, OnConflictExpr *onconflict, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
Plan *plan = &node->plan;
@@ -4918,6 +4918,30 @@ make_modifytable(PlannerInfo *root,
node->resultRelations = resultRelations;
node->resultRelIndex = -1; /* will be set correctly in setrefs.c */
node->plans = subplans;
+ if (!onconflict)
+ {
+ node->onConflictAction = ONCONFLICT_NONE;
+ node->onConflictSet = NIL;
+ node->onConflictWhere = NULL;
+ node->arbiterIndexes = NIL;
+ }
+ else
+ {
+ node->onConflictAction = onconflict->action;
+ node->onConflictSet = onconflict->onConflictSet;
+ node->onConflictWhere = onconflict->onConflictWhere;
+
+ /*
+ * If a set of unique index inference elements was provided (an
+ * INSERT...ON CONFLICT "inference specification"), then infer
+ * appropriate unique indexes (or throw an error if none are
+ * available).
+ */
+ node->arbiterIndexes = infer_arbiter_indexes(root);
+
+ node->exclRelRTI = onconflict->exclRelIndex;
+ node->exclRelTlist = onconflict->exclRelTlist;
+ }
node->withCheckOptionLists = withCheckOptionLists;
node->returningLists = returningLists;
node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index ea4d4c55cbd..c80d45acaa9 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -243,6 +243,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
result->queryId = parse->queryId;
result->hasReturning = (parse->returningList != NIL);
result->hasModifyingCTE = parse->hasModifyingCTE;
+ result->isUpsert =
+ (parse->onConflict && parse->onConflict->action == ONCONFLICT_UPDATE);
result->canSetTag = parse->canSetTag;
result->transientPlan = glob->transientPlan;
result->planTree = top_plan;
@@ -462,6 +464,17 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
parse->limitCount = preprocess_expression(root, parse->limitCount,
EXPRKIND_LIMIT);
+ if (parse->onConflict)
+ {
+ parse->onConflict->onConflictSet = (List *)
+ preprocess_expression(root, (Node *) parse->onConflict->onConflictSet,
+ EXPRKIND_TARGET);
+
+ parse->onConflict->onConflictWhere =
+ preprocess_expression(root, (Node *) parse->onConflict->onConflictWhere,
+ EXPRKIND_QUAL);
+ }
+
root->append_rel_list = (List *)
preprocess_expression(root, (Node *) root->append_rel_list,
EXPRKIND_APPINFO);
@@ -612,6 +625,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
withCheckOptionLists,
returningLists,
rowMarks,
+ parse->onConflict,
SS_assign_special_param(root));
}
}
@@ -802,6 +816,8 @@ inheritance_planner(PlannerInfo *root)
List *rowMarks;
ListCell *lc;
+ Assert(parse->commandType != CMD_INSERT);
+
/*
* We generate a modified instance of the original Query for each target
* relation, plan that, and put all the plans into a list that will be
@@ -1046,6 +1062,8 @@ inheritance_planner(PlannerInfo *root)
if (parse->returningList)
returningLists = lappend(returningLists,
subroot.parse->returningList);
+
+ Assert(!parse->onConflict);
}
/* Mark result as unordered (probably unnecessary) */
@@ -1095,6 +1113,7 @@ inheritance_planner(PlannerInfo *root)
withCheckOptionLists,
returningLists,
rowMarks,
+ NULL,
SS_assign_special_param(root));
}
@@ -1228,6 +1247,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
bool use_hashed_grouping = false;
WindowFuncLists *wflists = NULL;
List *activeWindows = NIL;
+ OnConflictExpr *onconfl;
MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
@@ -1242,6 +1262,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
/* Preprocess targetlist */
tlist = preprocess_targetlist(root, tlist);
+ onconfl = parse->onConflict;
+ if (onconfl)
+ onconfl->onConflictSet =
+ preprocess_onconflict_targetlist(onconfl->onConflictSet,
+ parse->resultRelation,
+ parse->rtable);
+
/*
* Expand any rangetable entries that have security barrier quals.
* This may add new security barrier subquery RTEs to the rangetable.
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b7d6ff11223..612d32571af 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -739,7 +739,35 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->plan.targetlist = copyObject(linitial(newRL));
}
+ /*
+ * We treat ModifyTable with ON CONFLICT as a form of 'pseudo
+ * join', where the inner side is the EXLUDED tuple. Therefore
+ * use fix_join_expr to setup the relevant variables to
+ * INNER_VAR. We explicitly don't create any OUTER_VARs as
+ * those are already used by RETURNING and it seems better to
+ * be non-conflicting.
+ */
+ if (splan->onConflictSet)
+ {
+ indexed_tlist *itlist;
+
+ itlist = build_tlist_index(splan->exclRelTlist);
+
+ splan->onConflictSet =
+ fix_join_expr(root, splan->onConflictSet,
+ NULL, itlist,
+ linitial_int(splan->resultRelations),
+ rtoffset);
+
+ splan->onConflictWhere = (Node *)
+ fix_join_expr(root, (List *) splan->onConflictWhere,
+ NULL, itlist,
+ linitial_int(splan->resultRelations),
+ rtoffset);
+ }
+
splan->nominalRelation += rtoffset;
+ splan->exclRelRTI += rtoffset;
foreach(l, splan->resultRelations)
{
@@ -1846,7 +1874,8 @@ search_indexed_tlist_for_sortgroupref(Node *node,
* inner_itlist = NULL and acceptable_rel = the ID of the target relation.
*
* 'clauses' is the targetlist or list of join clauses
- * 'outer_itlist' is the indexed target list of the outer join relation
+ * 'outer_itlist' is the indexed target list of the outer join relation,
+ * or NULL
* 'inner_itlist' is the indexed target list of the inner join relation,
* or NULL
* 'acceptable_rel' is either zero or the rangetable index of a relation
@@ -1886,12 +1915,17 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
Var *var = (Var *) node;
/* First look for the var in the input tlists */
- newvar = search_indexed_tlist_for_var(var,
- context->outer_itlist,
- OUTER_VAR,
- context->rtoffset);
- if (newvar)
- return (Node *) newvar;
+ if (context->outer_itlist)
+ {
+ newvar = search_indexed_tlist_for_var(var,
+ context->outer_itlist,
+ OUTER_VAR,
+ context->rtoffset);
+ if (newvar)
+ return (Node *) newvar;
+ }
+
+ /* Then in the outer */
if (context->inner_itlist)
{
newvar = search_indexed_tlist_for_var(var,
@@ -1920,7 +1954,7 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
PlaceHolderVar *phv = (PlaceHolderVar *) node;
/* See if the PlaceHolderVar has bubbled up from a lower plan node */
- if (context->outer_itlist->has_ph_vars)
+ if (context->outer_itlist && context->outer_itlist->has_ph_vars)
{
newvar = search_indexed_tlist_for_non_var((Node *) phv,
context->outer_itlist,
@@ -1943,7 +1977,7 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
if (IsA(node, Param))
return fix_param_node(context->root, (Param *) node);
/* Try matching more complex expressions too, if tlists have any */
- if (context->outer_itlist->has_non_vars)
+ if (context->outer_itlist && context->outer_itlist->has_non_vars)
{
newvar = search_indexed_tlist_for_non_var(node,
context->outer_itlist,
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index acfd0bcfbe5..0220672fc43 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2340,6 +2340,10 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
locally_added_param);
finalize_primnode((Node *) mtplan->returningLists,
&context);
+ finalize_primnode((Node *) mtplan->onConflictSet,
+ &context);
+ finalize_primnode((Node *) mtplan->onConflictWhere,
+ &context);
foreach(l, mtplan->plans)
{
context.paramids =
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 50acfe40e97..4f0dc80d025 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1030,6 +1030,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
pullup_replace_vars((Node *) parse->targetList, &rvcontext);
parse->returningList = (List *)
pullup_replace_vars((Node *) parse->returningList, &rvcontext);
+ if (parse->onConflict)
+ parse->onConflict->onConflictSet = (List *)
+ pullup_replace_vars((Node *) parse->onConflict->onConflictSet, &rvcontext);
replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
lowest_nulling_outer_join);
Assert(parse->setOperations == NULL);
@@ -1605,6 +1608,9 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
pullup_replace_vars((Node *) parse->targetList, &rvcontext);
parse->returningList = (List *)
pullup_replace_vars((Node *) parse->returningList, &rvcontext);
+ if (parse->onConflict)
+ parse->onConflict->onConflictSet = (List *)
+ pullup_replace_vars((Node *) parse->onConflict->onConflictSet, &rvcontext);
replace_vars_in_jointree((Node *) parse->jointree, &rvcontext, NULL);
Assert(parse->setOperations == NULL);
parse->havingQual = pullup_replace_vars(parse->havingQual, &rvcontext);
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 580c8467703..6b0c689e0c9 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -181,6 +181,19 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
return tlist;
}
+/*
+ * preprocess_onconflict_targetlist
+ * Process ON CONFLICT SET targetlist.
+ *
+ * Returns the new targetlist.
+ */
+List *
+preprocess_onconflict_targetlist(List *tlist, int result_relation, List *range_table)
+{
+ return expand_targetlist(tlist, CMD_UPDATE, result_relation, range_table);
+}
+
+
/*****************************************************************************
*
* TARGETLIST EXPANSION
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 068ab39dd43..8bcc5064a37 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -25,6 +25,7 @@
#include "access/transam.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
@@ -50,6 +51,8 @@ int constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
get_relation_info_hook_type get_relation_info_hook = NULL;
+static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
+ Bitmapset *inferAttrs, List *idxExprs);
static int32 get_rel_data_width(Relation rel, int32 *attr_widths);
static List *get_relation_constraints(PlannerInfo *root,
Oid relationObjectId, RelOptInfo *rel,
@@ -400,6 +403,355 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
}
/*
+ * infer_arbiter_indexes -
+ * Determine the unique indexes used to arbitrate speculative insertion.
+ *
+ * Uses user-supplied inference clause expressions and predicate to match a
+ * unique index from those defined and ready on the heap relation (target).
+ * An exact match is required on columns/expressions (although they can appear
+ * in any order). However, the predicate given by the user need only restrict
+ * insertion to a subset of some part of the table covered by some particular
+ * unique index (in particular, a partial unique index) in order to be
+ * inferred.
+ *
+ * The implementation does not consider which B-Tree operator class any
+ * particular available unique index attribute uses, unless one was specified
+ * in the inference specification. The same is true of collations. In
+ * particular, there is no system dependency on the default operator class for
+ * the purposes of inference. If no opclass (or collation) is specified, then
+ * all matching indexes (that may or may not match the default in terms of
+ * each attribute opclass/collation) are used for inference.
+ */
+List *
+infer_arbiter_indexes(PlannerInfo *root)
+{
+ OnConflictExpr *onconflict = root->parse->onConflict;
+ /* Iteration state */
+ Relation relation;
+ Oid relationObjectId;
+ Oid indexOidFromConstraint = InvalidOid;
+ List *indexList;
+ ListCell *l;
+
+ /* Normalized inference attributes and inference expressions: */
+ Bitmapset *inferAttrs = NULL;
+ List *inferElems = NIL;
+
+ /* Result */
+ List *candidates = NIL;
+
+ /*
+ * Quickly return NIL for ON CONFLICT DO NOTHING without an inference
+ * specification or named constraint. ON CONFLICT DO UPDATE statements
+ * must always provide one or the other (but parser ought to have caught
+ * that already).
+ */
+ if (onconflict->arbiterElems == NIL &&
+ onconflict->constraint == InvalidOid)
+ return NIL;
+
+ /*
+ * We need not lock the relation since it was already locked, either by
+ * the rewriter or when expand_inherited_rtentry() added it to the query's
+ * rangetable.
+ */
+ relationObjectId = rt_fetch(root->parse->resultRelation,
+ root->parse->rtable)->relid;
+
+ relation = heap_open(relationObjectId, NoLock);
+
+ /*
+ * Build normalized/BMS representation of plain indexed attributes, as
+ * well as direct list of inference elements. This is required for
+ * matching the cataloged definition of indexes.
+ */
+ foreach(l, onconflict->arbiterElems)
+ {
+ InferenceElem *elem;
+ Var *var;
+ int attno;
+
+ elem = (InferenceElem *) lfirst(l);
+
+ /*
+ * Parse analysis of inference elements performs full parse analysis
+ * of Vars, even for non-expression indexes (in contrast with utility
+ * command related use of IndexElem). However, indexes are cataloged
+ * with simple attribute numbers for non-expression indexes. Those
+ * are handled later.
+ */
+ if (!IsA(elem->expr, Var))
+ {
+ inferElems = lappend(inferElems, elem->expr);
+ continue;
+ }
+
+ var = (Var *) elem->expr;
+ attno = var->varattno;
+
+ if (attno < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("system columns cannot be used in an ON CONFLICT clause")));
+ else if (attno == 0)
+ elog(ERROR, "whole row unique index inference specifications are not valid");
+
+ inferAttrs = bms_add_member(inferAttrs, attno);
+ }
+
+ /*
+ * Lookup named constraint's index. This is not immediately returned
+ * because some additional sanity checks are required.
+ */
+ if (onconflict->constraint != InvalidOid)
+ {
+ indexOidFromConstraint = get_constraint_index(onconflict->constraint);
+
+ if (indexOidFromConstraint == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("constraint in ON CONFLICT clause has no associated index")));
+ }
+
+ indexList = RelationGetIndexList(relation);
+
+ /*
+ * Using that representation, iterate through the list of indexes on the
+ * target relation to try and find a match
+ */
+ foreach(l, indexList)
+ {
+ Oid indexoid = lfirst_oid(l);
+ Relation idxRel;
+ Form_pg_index idxForm;
+ Bitmapset *indexedAttrs = NULL;
+ List *idxExprs;
+ List *predExprs;
+ List *whereExplicit;
+ AttrNumber natt;
+ ListCell *el;
+
+ /*
+ * Extract info from the relation descriptor for the index. We know
+ * that this is a target, so get lock type it is known will ultimately
+ * be required by the executor.
+ *
+ * Let executor complain about !indimmediate case directly, because
+ * enforcement needs to occur there anyway when an inference clause is
+ * omitted.
+ */
+ idxRel = index_open(indexoid, RowExclusiveLock);
+ idxForm = idxRel->rd_index;
+
+ if (!IndexIsValid(idxForm))
+ goto next;
+
+ /*
+ * If the index is valid, but cannot yet be used, ignore it. See
+ * src/backend/access/heap/README.HOT for discussion.
+ */
+ if (idxForm->indcheckxmin &&
+ !TransactionIdPrecedes(HeapTupleHeaderGetXmin(idxRel->rd_indextuple->t_data),
+ TransactionXmin))
+ goto next;
+
+ /*
+ * Look for match on "ON constraint_name" variant, which may not be
+ * unique constraint. This can only be a constraint name.
+ */
+ if (indexOidFromConstraint == idxForm->indexrelid)
+ {
+ if (!idxForm->indisunique && onconflict->action == ONCONFLICT_UPDATE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("ON CONFLICT DO UPDATE not supported with exclusion constraints")));
+
+ list_free(indexList);
+ index_close(idxRel, NoLock);
+ heap_close(relation, NoLock);
+ candidates = lappend_oid(candidates, idxForm->indexrelid);
+ return candidates;
+ }
+ else if (indexOidFromConstraint != InvalidOid)
+ {
+ /* No point in further work for index in named constraint case */
+ goto next;
+ }
+
+ /*
+ * Only considering conventional inference at this point (not named
+ * constraints), so index under consideration can be immediately
+ * skipped if it's not unique
+ */
+ if (!idxForm->indisunique)
+ goto next;
+
+ /* Build BMS representation of cataloged index attributes */
+ for (natt = 0; natt < idxForm->indnatts; natt++)
+ {
+ int attno = idxRel->rd_index->indkey.values[natt];
+
+ if (attno < 0)
+ elog(ERROR, "system column in index");
+
+ if (attno != 0)
+ indexedAttrs = bms_add_member(indexedAttrs, attno);
+ }
+
+ /* Non-expression attributes (if any) must match */
+ if (!bms_equal(indexedAttrs, inferAttrs))
+ goto next;
+
+ /* Expression attributes (if any) must match */
+ idxExprs = RelationGetIndexExpressions(idxRel);
+ foreach(el, onconflict->arbiterElems)
+ {
+ InferenceElem *elem = (InferenceElem *) lfirst(el);
+
+ /*
+ * Ensure that collation/opclass aspects of inference expression
+ * element match. Even though this loop is primarily concerned
+ * with matching expressions, it is a convenient point to check
+ * this for both expressions and ordinary (non-expression)
+ * attributes appearing as inference elements.
+ */
+ if (!infer_collation_opclass_match(elem, idxRel, inferAttrs,
+ idxExprs))
+ goto next;
+
+ /*
+ * Plain Vars don't factor into count of expression elements, and
+ * the question of whether or not they satisfy the index
+ * definition has already been considered (they must).
+ */
+ if (IsA(elem->expr, Var))
+ continue;
+
+ /*
+ * Might as well avoid redundant check in the rare cases where
+ * infer_collation_opclass_match() is required to do real work.
+ * Otherwise, check that element expression appears in cataloged
+ * index definition.
+ */
+ if (elem->infercollid != InvalidOid ||
+ elem->inferopfamily != InvalidOid ||
+ list_member(idxExprs, elem->expr))
+ continue;
+
+ goto next;
+ }
+
+ /*
+ * Now that all inference elements were matched, ensure that the
+ * expression elements from inference clause are not missing any
+ * cataloged expressions. This does the right thing when unique
+ * indexes redundantly repeat the same attribute, or if attributes
+ * redundantly appear multiple times within an inference clause.
+ */
+ if (list_difference(idxExprs, inferElems) != NIL)
+ goto next;
+
+ /*
+ * Any user-supplied ON CONFLICT unique index inference WHERE clause
+ * need only be implied by the cataloged index definitions predicate.
+ */
+ predExprs = RelationGetIndexPredicate(idxRel);
+ whereExplicit = make_ands_implicit((Expr *) onconflict->arbiterWhere);
+
+ if (!predicate_implied_by(predExprs, whereExplicit))
+ goto next;
+
+ candidates = lappend_oid(candidates, idxForm->indexrelid);
+next:
+ index_close(idxRel, NoLock);
+ }
+
+ list_free(indexList);
+ heap_close(relation, NoLock);
+
+ if (candidates == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("there is no unique or exclusion constraint matching the ON CONFLICT specification")));
+
+ return candidates;
+}
+
+/*
+ * infer_collation_opclass_match - ensure infer element opclass/collation match
+ *
+ * Given unique index inference element from inference specification, if
+ * collation was specified, or if opclass (represented here as opfamily +
+ * opcintype) was specified, verify that there is at least one matching
+ * indexed attribute (occasionally, there may be more). Skip this in the
+ * common case where inference specification does not include collation or
+ * opclass (instead matching everything, regardless of cataloged
+ * collation/opclass of indexed attribute).
+ *
+ * At least historically, Postgres has not offered collations or opclasses
+ * with alternative-to-default notions of equality, so these additional
+ * criteria should only be required infrequently.
+ *
+ * Don't give up immediately when an inference element matches some attribute
+ * cataloged as indexed but not matching additional opclass/collation
+ * criteria. This is done so that the implementation is as forgiving as
+ * possible of redundancy within cataloged index attributes (or, less
+ * usefully, within inference specification elements). If collations actually
+ * differ between apparently redundantly indexed attributes (redundant within
+ * or across indexes), then there really is no redundancy as such.
+ *
+ * Note that if an inference element specifies an opclass and a collation at
+ * once, both must match in at least one particular attribute within index
+ * catalog definition in order for that inference element to be considered
+ * inferred/satisfied.
+ */
+static bool
+infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
+ Bitmapset *inferAttrs, List *idxExprs)
+{
+ AttrNumber natt;
+
+ /*
+ * If inference specification element lacks collation/opclass, then no
+ * need to check for exact match.
+ */
+ if (elem->infercollid == InvalidOid && elem->inferopfamily == InvalidOid)
+ return true;
+
+ for (natt = 1; natt <= idxRel->rd_att->natts; natt++)
+ {
+ Oid opfamily = idxRel->rd_opfamily[natt - 1];
+ Oid opcinputtype = idxRel->rd_opcintype[natt - 1];
+ Oid collation = idxRel->rd_indcollation[natt - 1];
+
+ if (elem->inferopfamily != InvalidOid &&
+ (elem->inferopfamily != opfamily ||
+ elem->inferopcinputtype != opcinputtype))
+ {
+ /* Attribute needed to match opclass, but didn't */
+ continue;
+ }
+
+ if (elem->infercollid != InvalidOid &&
+ elem->infercollid != collation)
+ {
+ /* Attribute needed to match collation, but didn't */
+ continue;
+ }
+
+ if ((IsA(elem->expr, Var) &&
+ bms_is_member(((Var *) elem->expr)->varattno, inferAttrs)) ||
+ list_member(idxExprs, elem->expr))
+ {
+ /* Found one match - good enough */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
* estimate_rel_size - estimate # pages and # tuples in a table or index
*
* We also estimate the fraction of the pages that are marked all-visible in