mirror of
https://github.com/postgres/postgres.git
synced 2025-11-21 00:42:43 +03:00
Reduce "Var IS [NOT] NULL" quals during constant folding
In commit b262ad440, we introduced an optimization that reduces an IS
[NOT] NULL qual on a NOT NULL column to constant true or constant
false, provided we can prove that the input expression of the NullTest
is not nullable by any outer joins or grouping sets. This deduction
happens quite late in the planner, during the distribution of quals to
rels in query_planner. However, this approach has some drawbacks: we
can't perform any further folding with the constant, and it turns out
to be prone to bugs.
Ideally, this deduction should happen during constant folding.
However, the per-relation information about which columns are defined
as NOT NULL is not available at that point. This information is
currently collected from catalogs when building RelOptInfos for base
or "other" relations.
This patch moves the collection of NOT NULL attribute information for
relations before pull_up_sublinks, storing it in a hash table keyed by
relation OID. It then uses this information to perform the NullTest
deduction for Vars during constant folding. This also makes it
possible to leverage this information to pull up NOT IN subqueries.
Note that this patch does not get rid of restriction_is_always_true
and restriction_is_always_false. Removing them would prevent us from
reducing some IS [NOT] NULL quals that we were previously able to
reduce, because (a) the self-join elimination may introduce new IS NOT
NULL quals after constant folding, and (b) if some outer joins are
converted to inner joins, previously irreducible NullTest quals may
become reducible.
Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/CAMbWs4-bFJ1At4btk5wqbezdu8PLtQ3zv-aiaY3ry9Ymm=jgFQ@mail.gmail.com
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/htup_details.h"
|
||||
#include "catalog/pg_class.h"
|
||||
#include "catalog/pg_language.h"
|
||||
#include "catalog/pg_operator.h"
|
||||
#include "catalog/pg_proc.h"
|
||||
@@ -36,6 +37,7 @@
|
||||
#include "optimizer/clauses.h"
|
||||
#include "optimizer/cost.h"
|
||||
#include "optimizer/optimizer.h"
|
||||
#include "optimizer/pathnode.h"
|
||||
#include "optimizer/plancat.h"
|
||||
#include "optimizer/planmain.h"
|
||||
#include "parser/analyze.h"
|
||||
@@ -43,6 +45,7 @@
|
||||
#include "parser/parse_collate.h"
|
||||
#include "parser/parse_func.h"
|
||||
#include "parser/parse_oper.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#include "rewrite/rewriteManip.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
@@ -2242,7 +2245,8 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum,
|
||||
* only operators and functions that are reasonable to try to execute.
|
||||
*
|
||||
* NOTE: "root" can be passed as NULL if the caller never wants to do any
|
||||
* Param substitutions nor receive info about inlined functions.
|
||||
* Param substitutions nor receive info about inlined functions nor reduce
|
||||
* NullTest for Vars to constant true or constant false.
|
||||
*
|
||||
* NOTE: the planner assumes that this will always flatten nested AND and
|
||||
* OR clauses into N-argument form. See comments in prepqual.c.
|
||||
@@ -3544,6 +3548,31 @@ eval_const_expressions_mutator(Node *node,
|
||||
|
||||
return makeBoolConst(result, false);
|
||||
}
|
||||
if (!ntest->argisrow && arg && IsA(arg, Var) && context->root)
|
||||
{
|
||||
Var *varg = (Var *) arg;
|
||||
bool result;
|
||||
|
||||
if (var_is_nonnullable(context->root, varg, false))
|
||||
{
|
||||
switch (ntest->nulltesttype)
|
||||
{
|
||||
case IS_NULL:
|
||||
result = false;
|
||||
break;
|
||||
case IS_NOT_NULL:
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized nulltesttype: %d",
|
||||
(int) ntest->nulltesttype);
|
||||
result = false; /* keep compiler quiet */
|
||||
break;
|
||||
}
|
||||
|
||||
return makeBoolConst(result, false);
|
||||
}
|
||||
}
|
||||
|
||||
newntest = makeNode(NullTest);
|
||||
newntest->arg = (Expr *) arg;
|
||||
@@ -4162,6 +4191,67 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
|
||||
return newexpr;
|
||||
}
|
||||
|
||||
/*
|
||||
* var_is_nonnullable: check to see if the Var cannot be NULL
|
||||
*
|
||||
* If the Var is defined NOT NULL and meanwhile is not nulled by any outer
|
||||
* joins or grouping sets, then we can know that it cannot be NULL.
|
||||
*
|
||||
* use_rel_info indicates whether the corresponding RelOptInfo is available for
|
||||
* use.
|
||||
*/
|
||||
bool
|
||||
var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info)
|
||||
{
|
||||
Relids notnullattnums = NULL;
|
||||
|
||||
Assert(IsA(var, Var));
|
||||
|
||||
/* skip upper-level Vars */
|
||||
if (var->varlevelsup != 0)
|
||||
return false;
|
||||
|
||||
/* could the Var be nulled by any outer joins or grouping sets? */
|
||||
if (!bms_is_empty(var->varnullingrels))
|
||||
return false;
|
||||
|
||||
/* system columns cannot be NULL */
|
||||
if (var->varattno < 0)
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Check if the Var is defined as NOT NULL. We retrieve the column NOT
|
||||
* NULL constraint information from the corresponding RelOptInfo if it is
|
||||
* available; otherwise, we search the hash table for this information.
|
||||
*/
|
||||
if (use_rel_info)
|
||||
{
|
||||
RelOptInfo *rel = find_base_rel(root, var->varno);
|
||||
|
||||
notnullattnums = rel->notnullattnums;
|
||||
}
|
||||
else
|
||||
{
|
||||
RangeTblEntry *rte = planner_rt_fetch(var->varno, root);
|
||||
|
||||
/*
|
||||
* We must skip inheritance parent tables, as some child tables may
|
||||
* have a NOT NULL constraint for a column while others may not. This
|
||||
* cannot happen with partitioned tables, though.
|
||||
*/
|
||||
if (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE)
|
||||
return false;
|
||||
|
||||
notnullattnums = find_relation_notnullatts(root, rte->relid);
|
||||
}
|
||||
|
||||
if (var->varattno > 0 &&
|
||||
bms_is_member(var->varattno, notnullattnums))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* expand_function_arguments: convert named-notation args to positional args
|
||||
* and/or insert default args, as needed
|
||||
|
||||
Reference in New Issue
Block a user