mirror of
https://github.com/postgres/postgres.git
synced 2025-05-09 18:21:05 +03:00
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
This commit is contained in:
parent
8f321bd16c
commit
e83daa7e33
@ -994,7 +994,63 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* Check if the expression the right shape (one Var, one Const) */
|
/* 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;
|
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.
|
* on which side of the operator we found the Var node.
|
||||||
*/
|
*/
|
||||||
bool
|
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;
|
Var *var;
|
||||||
Const *cst;
|
Const *cst;
|
||||||
@ -1405,10 +1461,10 @@ examine_opclause_expression(OpExpr *expr, Var **varp, Const **cstp, bool *varonl
|
|||||||
*rightop;
|
*rightop;
|
||||||
|
|
||||||
/* enforced by statext_is_compatible_clause_internal */
|
/* enforced by statext_is_compatible_clause_internal */
|
||||||
Assert(list_length(expr->args) == 2);
|
Assert(list_length(args) == 2);
|
||||||
|
|
||||||
leftop = linitial(expr->args);
|
leftop = linitial(args);
|
||||||
rightop = lsecond(expr->args);
|
rightop = lsecond(args);
|
||||||
|
|
||||||
/* strip RelabelType from either side of the expression */
|
/* strip RelabelType from either side of the expression */
|
||||||
if (IsA(leftop, RelabelType))
|
if (IsA(leftop, RelabelType))
|
||||||
|
@ -1579,7 +1579,7 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
|
|||||||
OpExpr *expr = (OpExpr *) clause;
|
OpExpr *expr = (OpExpr *) clause;
|
||||||
FmgrInfo opproc;
|
FmgrInfo opproc;
|
||||||
|
|
||||||
/* valid only after examine_opclause_expression returns true */
|
/* valid only after examine_clause_args returns true */
|
||||||
Var *var;
|
Var *var;
|
||||||
Const *cst;
|
Const *cst;
|
||||||
bool varonleft;
|
bool varonleft;
|
||||||
@ -1587,7 +1587,7 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
|
|||||||
fmgr_info(get_opcode(expr->opno), &opproc);
|
fmgr_info(get_opcode(expr->opno), &opproc);
|
||||||
|
|
||||||
/* extract the var and const from the expression */
|
/* 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;
|
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))
|
else if (IsA(clause, NullTest))
|
||||||
{
|
{
|
||||||
NullTest *expr = (NullTest *) clause;
|
NullTest *expr = (NullTest *) clause;
|
||||||
|
@ -96,8 +96,8 @@ extern SortItem *build_sorted_items(int numrows, int *nitems, HeapTuple *rows,
|
|||||||
TupleDesc tdesc, MultiSortSupport mss,
|
TupleDesc tdesc, MultiSortSupport mss,
|
||||||
int numattrs, AttrNumber *attnums);
|
int numattrs, AttrNumber *attnums);
|
||||||
|
|
||||||
extern bool examine_opclause_expression(OpExpr *expr, Var **varp,
|
extern bool examine_clause_args(List *args, Var **varp,
|
||||||
Const **cstp, bool *varonleftp);
|
Const **cstp, bool *varonleftp);
|
||||||
|
|
||||||
extern Selectivity mcv_clauselist_selectivity(PlannerInfo *root,
|
extern Selectivity mcv_clauselist_selectivity(PlannerInfo *root,
|
||||||
StatisticExtInfo *stat,
|
StatisticExtInfo *stat,
|
||||||
|
@ -827,6 +827,36 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = '
|
|||||||
343 | 200
|
343 | 200
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52) AND b IN ( ''1'', ''2'')');
|
||||||
|
estimated | actual
|
||||||
|
-----------+--------
|
||||||
|
8 | 200
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])');
|
||||||
|
estimated | actual
|
||||||
|
-----------+--------
|
||||||
|
8 | 200
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, 2, 3]) AND b IN (''1'', ''2'', ''3'')');
|
||||||
|
estimated | actual
|
||||||
|
-----------+--------
|
||||||
|
26 | 150
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3])');
|
||||||
|
estimated | actual
|
||||||
|
-----------+--------
|
||||||
|
10 | 100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', ''3'') AND c > ANY (ARRAY[1, 2, 3])');
|
||||||
|
estimated | actual
|
||||||
|
-----------+--------
|
||||||
|
1 | 100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
-- create statistics
|
-- create statistics
|
||||||
CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists;
|
CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists;
|
||||||
ANALYZE mcv_lists;
|
ANALYZE mcv_lists;
|
||||||
@ -872,6 +902,36 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = '
|
|||||||
200 | 200
|
200 | 200
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52) AND b IN ( ''1'', ''2'')');
|
||||||
|
estimated | actual
|
||||||
|
-----------+--------
|
||||||
|
200 | 200
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])');
|
||||||
|
estimated | actual
|
||||||
|
-----------+--------
|
||||||
|
200 | 200
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, 2, 3]) AND b IN (''1'', ''2'', ''3'')');
|
||||||
|
estimated | actual
|
||||||
|
-----------+--------
|
||||||
|
150 | 150
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3])');
|
||||||
|
estimated | actual
|
||||||
|
-----------+--------
|
||||||
|
100 | 100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', ''3'') AND c > ANY (ARRAY[1, 2, 3])');
|
||||||
|
estimated | actual
|
||||||
|
-----------+--------
|
||||||
|
100 | 100
|
||||||
|
(1 row)
|
||||||
|
|
||||||
-- we can't use the statistic for OR clauses that are not fully covered (missing 'd' attribute)
|
-- we can't use the statistic for OR clauses that are not fully covered (missing 'd' attribute)
|
||||||
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL');
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL');
|
||||||
estimated | actual
|
estimated | actual
|
||||||
|
@ -461,6 +461,16 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = '
|
|||||||
|
|
||||||
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL');
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL');
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52) AND b IN ( ''1'', ''2'')');
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])');
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, 2, 3]) AND b IN (''1'', ''2'', ''3'')');
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3])');
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', ''3'') AND c > ANY (ARRAY[1, 2, 3])');
|
||||||
|
|
||||||
-- create statistics
|
-- create statistics
|
||||||
CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists;
|
CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists;
|
||||||
|
|
||||||
@ -480,6 +490,16 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= 4 AND b <
|
|||||||
|
|
||||||
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1');
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1');
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a IN (1, 2, 51, 52) AND b IN ( ''1'', ''2'')');
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])');
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a <= ANY (ARRAY[1, 2, 3]) AND b IN (''1'', ''2'', ''3'')');
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND c > ANY (ARRAY[1, 2, 3])');
|
||||||
|
|
||||||
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', ''3'') AND c > ANY (ARRAY[1, 2, 3])');
|
||||||
|
|
||||||
-- we can't use the statistic for OR clauses that are not fully covered (missing 'd' attribute)
|
-- we can't use the statistic for OR clauses that are not fully covered (missing 'd' attribute)
|
||||||
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL');
|
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 OR b = ''1'' OR c = 1 OR d IS NOT NULL');
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user