summaryrefslogtreecommitdiff
path: root/src/backend/statistics
diff options
context:
space:
mode:
authorTomas Vondra2020-03-14 13:56:37 +0000
committerTomas Vondra2020-03-14 15:13:00 +0000
commite83daa7e331e0718bb254ab685f2ed95df423554 (patch)
tree277ffc346c2d8c579ccfc1867192f031bbc816df /src/backend/statistics
parent8f321bd16cdb11307f17007eb3c55b65d7d323ef (diff)
Use multi-variate MCV lists to estimate ScalarArrayOpExpr
Commit 8f321bd16c added support for estimating ScalarArrayOpExpr clauses (IN/ANY) clauses using functional dependencies. There's no good reason not to support estimation of these clauses using multi-variate MCV lists too, so this commits implements that. That makes the behavior consistent and MCV lists can estimate all variants (ANY/ALL, inequalities, ...). Author: Tomas Vondra Review: Dean Rasheed Discussion: https://www.postgresql.org/message-id/flat/13902317.Eha0YfKkKy%40pierred-pdoc
Diffstat (limited to 'src/backend/statistics')
-rw-r--r--src/backend/statistics/extended_stats.c66
-rw-r--r--src/backend/statistics/mcv.c111
2 files changed, 170 insertions, 7 deletions
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 81864ce03df..c3f0da4e233 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -994,7 +994,63 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
return false;
/* Check if the expression the right shape (one Var, one Const) */
- if (!examine_opclause_expression(expr, &var, NULL, NULL))
+ if (!examine_clause_args(expr->args, &var, NULL, NULL))
+ return false;
+
+ /*
+ * If it's not one of the supported operators ("=", "<", ">", etc.),
+ * just ignore the clause, as it's not compatible with MCV lists.
+ *
+ * This uses the function for estimating selectivity, not the operator
+ * directly (a bit awkward, but well ...).
+ */
+ switch (get_oprrest(expr->opno))
+ {
+ case F_EQSEL:
+ case F_NEQSEL:
+ case F_SCALARLTSEL:
+ case F_SCALARLESEL:
+ case F_SCALARGTSEL:
+ case F_SCALARGESEL:
+ /* supported, will continue with inspection of the Var */
+ break;
+
+ default:
+ /* other estimators are considered unknown/unsupported */
+ return false;
+ }
+
+ /*
+ * If there are any securityQuals on the RTE from security barrier
+ * views or RLS policies, then the user may not have access to all the
+ * table's data, and we must check that the operator is leak-proof.
+ *
+ * If the operator is leaky, then we must ignore this clause for the
+ * purposes of estimating with MCV lists, otherwise the operator might
+ * reveal values from the MCV list that the user doesn't have
+ * permission to see.
+ */
+ if (rte->securityQuals != NIL &&
+ !get_func_leakproof(get_opcode(expr->opno)))
+ return false;
+
+ return statext_is_compatible_clause_internal(root, (Node *) var,
+ relid, attnums);
+ }
+
+ /* Var IN Array */
+ if (IsA(clause, ScalarArrayOpExpr))
+ {
+ RangeTblEntry *rte = root->simple_rte_array[relid];
+ ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) clause;
+ Var *var;
+
+ /* Only expressions with two arguments are considered compatible. */
+ if (list_length(expr->args) != 2)
+ return false;
+
+ /* Check if the expression the right shape (one Var, one Const) */
+ if (!examine_clause_args(expr->args, &var, NULL, NULL))
return false;
/*
@@ -1396,7 +1452,7 @@ statext_clauselist_selectivity(PlannerInfo *root, List *clauses, int varRelid,
* on which side of the operator we found the Var node.
*/
bool
-examine_opclause_expression(OpExpr *expr, Var **varp, Const **cstp, bool *varonleftp)
+examine_clause_args(List *args, Var **varp, Const **cstp, bool *varonleftp)
{
Var *var;
Const *cst;
@@ -1405,10 +1461,10 @@ examine_opclause_expression(OpExpr *expr, Var **varp, Const **cstp, bool *varonl
*rightop;
/* enforced by statext_is_compatible_clause_internal */
- Assert(list_length(expr->args) == 2);
+ Assert(list_length(args) == 2);
- leftop = linitial(expr->args);
- rightop = lsecond(expr->args);
+ leftop = linitial(args);
+ rightop = lsecond(args);
/* strip RelabelType from either side of the expression */
if (IsA(leftop, RelabelType))
diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c
index 87e232fdd4e..3147d8fedc6 100644
--- a/src/backend/statistics/mcv.c
+++ b/src/backend/statistics/mcv.c
@@ -1579,7 +1579,7 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
OpExpr *expr = (OpExpr *) clause;
FmgrInfo opproc;
- /* valid only after examine_opclause_expression returns true */
+ /* valid only after examine_clause_args returns true */
Var *var;
Const *cst;
bool varonleft;
@@ -1587,7 +1587,7 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
fmgr_info(get_opcode(expr->opno), &opproc);
/* extract the var and const from the expression */
- if (examine_opclause_expression(expr, &var, &cst, &varonleft))
+ if (examine_clause_args(expr->args, &var, &cst, &varonleft))
{
int idx;
@@ -1652,6 +1652,113 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
}
}
}
+ else if (IsA(clause, ScalarArrayOpExpr))
+ {
+ ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) clause;
+ FmgrInfo opproc;
+
+ /* valid only after examine_clause_args returns true */
+ Var *var;
+ Const *cst;
+ bool varonleft;
+
+ fmgr_info(get_opcode(expr->opno), &opproc);
+
+ /* extract the var and const from the expression */
+ if (examine_clause_args(expr->args, &var, &cst, &varonleft))
+ {
+ int idx;
+
+ ArrayType *arrayval;
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
+ int num_elems;
+ Datum *elem_values;
+ bool *elem_nulls;
+
+ /* ScalarArrayOpExpr has the Var always on the left */
+ Assert(varonleft);
+
+ if (!cst->constisnull)
+ {
+ arrayval = DatumGetArrayTypeP(cst->constvalue);
+ get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(arrayval,
+ ARR_ELEMTYPE(arrayval),
+ elmlen, elmbyval, elmalign,
+ &elem_values, &elem_nulls, &num_elems);
+ }
+
+ /* match the attribute to a dimension of the statistic */
+ idx = bms_member_index(keys, var->varattno);
+
+ /*
+ * Walk through the MCV items and evaluate the current clause.
+ * We can skip items that were already ruled out, and
+ * terminate if there are no remaining MCV items that might
+ * possibly match.
+ */
+ for (i = 0; i < mcvlist->nitems; i++)
+ {
+ int j;
+ bool match = (expr->useOr ? false : true);
+ MCVItem *item = &mcvlist->items[i];
+
+ /*
+ * When the MCV item or the Const value is NULL we can treat
+ * this as a mismatch. We must not call the operator because
+ * of strictness.
+ */
+ if (item->isnull[idx] || cst->constisnull)
+ {
+ matches[i] = RESULT_MERGE(matches[i], is_or, false);
+ continue;
+ }
+
+ /*
+ * Skip MCV items that can't change result in the bitmap.
+ * Once the value gets false for AND-lists, or true for
+ * OR-lists, we don't need to look at more clauses.
+ */
+ if (RESULT_IS_FINAL(matches[i], is_or))
+ continue;
+
+ for (j = 0; j < num_elems; j++)
+ {
+ Datum elem_value = elem_values[j];
+ bool elem_isnull = elem_nulls[j];
+ bool elem_match;
+
+ /* NULL values always evaluate as not matching. */
+ if (elem_isnull)
+ {
+ match = RESULT_MERGE(match, expr->useOr, false);
+ continue;
+ }
+
+ /*
+ * Stop evaluating the array elements once we reach
+ * match value that can't change - ALL() is the same
+ * as AND-list, ANY() is the same as OR-list.
+ */
+ if (RESULT_IS_FINAL(match, expr->useOr))
+ break;
+
+ elem_match = DatumGetBool(FunctionCall2Coll(&opproc,
+ var->varcollid,
+ item->values[idx],
+ elem_value));
+
+ match = RESULT_MERGE(match, expr->useOr, elem_match);
+ }
+
+ /* update the match bitmap with the result */
+ matches[i] = RESULT_MERGE(matches[i], is_or, match);
+ }
+ }
+ }
else if (IsA(clause, NullTest))
{
NullTest *expr = (NullTest *) clause;