mirror of
https://github.com/postgres/postgres.git
synced 2025-07-27 12:41:57 +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:
@ -710,12 +710,12 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- Op
|
|||||||
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1")))
|
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1")))
|
||||||
(3 rows)
|
(3 rows)
|
||||||
|
|
||||||
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
|
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c3 IS NOT NULL) IS DISTINCT FROM (c3 IS NOT NULL); -- DistinctExpr
|
||||||
QUERY PLAN
|
QUERY PLAN
|
||||||
--------------------------------------------------------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------------------------------------------------
|
||||||
Foreign Scan on public.ft1 t1
|
Foreign Scan on public.ft1 t1
|
||||||
Output: c1, c2, c3, c4, c5, c6, c7, c8
|
Output: c1, c2, c3, c4, c5, c6, c7, c8
|
||||||
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL)))
|
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((c3 IS NOT NULL) IS DISTINCT FROM (c3 IS NOT NULL)))
|
||||||
(3 rows)
|
(3 rows)
|
||||||
|
|
||||||
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
|
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
|
||||||
|
@ -352,7 +352,7 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c3 IS NULL; -- Nu
|
|||||||
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c3 IS NOT NULL; -- NullTest
|
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c3 IS NOT NULL; -- NullTest
|
||||||
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
|
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
|
||||||
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l)
|
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l)
|
||||||
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
|
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c3 IS NOT NULL) IS DISTINCT FROM (c3 IS NOT NULL); -- DistinctExpr
|
||||||
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
|
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
|
||||||
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef
|
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef
|
||||||
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars
|
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars
|
||||||
|
@ -3048,36 +3048,16 @@ add_base_clause_to_rel(PlannerInfo *root, Index relid,
|
|||||||
* expr_is_nonnullable
|
* expr_is_nonnullable
|
||||||
* Check to see if the Expr cannot be NULL
|
* Check to see if the Expr cannot be NULL
|
||||||
*
|
*
|
||||||
* If the Expr is a simple Var that is defined NOT NULL and meanwhile is not
|
* Currently we only support simple Vars.
|
||||||
* nulled by any outer joins, then we can know that it cannot be NULL.
|
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
expr_is_nonnullable(PlannerInfo *root, Expr *expr)
|
expr_is_nonnullable(PlannerInfo *root, Expr *expr)
|
||||||
{
|
{
|
||||||
RelOptInfo *rel;
|
|
||||||
Var *var;
|
|
||||||
|
|
||||||
/* For now only check simple Vars */
|
/* For now only check simple Vars */
|
||||||
if (!IsA(expr, Var))
|
if (!IsA(expr, Var))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var = (Var *) expr;
|
return var_is_nonnullable(root, (Var *) expr, true);
|
||||||
|
|
||||||
/* could the Var be nulled by any outer joins? */
|
|
||||||
if (!bms_is_empty(var->varnullingrels))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* system columns cannot be NULL */
|
|
||||||
if (var->varattno < 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
/* is the column defined NOT NULL? */
|
|
||||||
rel = find_base_rel(root, var->varno);
|
|
||||||
if (var->varattno > 0 &&
|
|
||||||
bms_is_member(var->varattno, rel->notnullattnums))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -342,6 +342,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
|
|||||||
glob->transientPlan = false;
|
glob->transientPlan = false;
|
||||||
glob->dependsOnRole = false;
|
glob->dependsOnRole = false;
|
||||||
glob->partition_directory = NULL;
|
glob->partition_directory = NULL;
|
||||||
|
glob->rel_notnullatts_hash = NULL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assess whether it's feasible to use parallel mode for this query. We
|
* Assess whether it's feasible to use parallel mode for this query. We
|
||||||
@ -723,11 +724,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root,
|
|||||||
/*
|
/*
|
||||||
* Scan the rangetable for relation RTEs and retrieve the necessary
|
* Scan the rangetable for relation RTEs and retrieve the necessary
|
||||||
* catalog information for each relation. Using this information, clear
|
* catalog information for each relation. Using this information, clear
|
||||||
* the inh flag for any relation that has no children, and expand virtual
|
* the inh flag for any relation that has no children, collect not-null
|
||||||
* generated columns for any relation that contains them. Note that this
|
* attribute numbers for any relation that has column not-null
|
||||||
* step does not descend into sublinks and subqueries; if we pull up any
|
* constraints, and expand virtual generated columns for any relation that
|
||||||
* sublinks or subqueries below, their relation RTEs are processed just
|
* contains them. Note that this step does not descend into sublinks and
|
||||||
* before pulling them up.
|
* subqueries; if we pull up any sublinks or subqueries below, their
|
||||||
|
* relation RTEs are processed just before pulling them up.
|
||||||
*/
|
*/
|
||||||
parse = root->parse = preprocess_relation_rtes(root);
|
parse = root->parse = preprocess_relation_rtes(root);
|
||||||
|
|
||||||
|
@ -1519,8 +1519,10 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
|
|||||||
/*
|
/*
|
||||||
* Scan the rangetable for relation RTEs and retrieve the necessary
|
* Scan the rangetable for relation RTEs and retrieve the necessary
|
||||||
* catalog information for each relation. Using this information, clear
|
* catalog information for each relation. Using this information, clear
|
||||||
* the inh flag for any relation that has no children, and expand virtual
|
* the inh flag for any relation that has no children, collect not-null
|
||||||
* generated columns for any relation that contains them.
|
* attribute numbers for any relation that has column not-null
|
||||||
|
* constraints, and expand virtual generated columns for any relation that
|
||||||
|
* contains them.
|
||||||
*
|
*
|
||||||
* Note: we construct up an entirely dummy PlannerInfo for use here. This
|
* Note: we construct up an entirely dummy PlannerInfo for use here. This
|
||||||
* is fine because only the "glob" and "parse" links will be used in this
|
* is fine because only the "glob" and "parse" links will be used in this
|
||||||
@ -1760,6 +1762,7 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
|
|||||||
Node **testexpr, List **paramIds)
|
Node **testexpr, List **paramIds)
|
||||||
{
|
{
|
||||||
Node *whereClause;
|
Node *whereClause;
|
||||||
|
PlannerInfo subroot;
|
||||||
List *leftargs,
|
List *leftargs,
|
||||||
*rightargs,
|
*rightargs,
|
||||||
*opids,
|
*opids,
|
||||||
@ -1819,12 +1822,15 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
|
|||||||
* parent aliases were flattened already, and we're not going to pull any
|
* parent aliases were flattened already, and we're not going to pull any
|
||||||
* child Vars (of any description) into the parent.
|
* child Vars (of any description) into the parent.
|
||||||
*
|
*
|
||||||
* Note: passing the parent's root to eval_const_expressions is
|
* Note: we construct up an entirely dummy PlannerInfo to pass to
|
||||||
* technically wrong, but we can get away with it since only the
|
* eval_const_expressions. This is fine because only the "glob" and
|
||||||
* boundParams (if any) are used, and those would be the same in a
|
* "parse" links are used by eval_const_expressions.
|
||||||
* subroot.
|
|
||||||
*/
|
*/
|
||||||
whereClause = eval_const_expressions(root, whereClause);
|
MemSet(&subroot, 0, sizeof(subroot));
|
||||||
|
subroot.type = T_PlannerInfo;
|
||||||
|
subroot.glob = root->glob;
|
||||||
|
subroot.parse = subselect;
|
||||||
|
whereClause = eval_const_expressions(&subroot, whereClause);
|
||||||
whereClause = (Node *) canonicalize_qual((Expr *) whereClause, false);
|
whereClause = (Node *) canonicalize_qual((Expr *) whereClause, false);
|
||||||
whereClause = (Node *) make_ands_implicit((Expr *) whereClause);
|
whereClause = (Node *) make_ands_implicit((Expr *) whereClause);
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "optimizer/clauses.h"
|
#include "optimizer/clauses.h"
|
||||||
#include "optimizer/optimizer.h"
|
#include "optimizer/optimizer.h"
|
||||||
#include "optimizer/placeholder.h"
|
#include "optimizer/placeholder.h"
|
||||||
|
#include "optimizer/plancat.h"
|
||||||
#include "optimizer/prep.h"
|
#include "optimizer/prep.h"
|
||||||
#include "optimizer/subselect.h"
|
#include "optimizer/subselect.h"
|
||||||
#include "optimizer/tlist.h"
|
#include "optimizer/tlist.h"
|
||||||
@ -401,8 +402,9 @@ transform_MERGE_to_join(Query *parse)
|
|||||||
*
|
*
|
||||||
* This scans the rangetable for relation RTEs and retrieves the necessary
|
* This scans the rangetable for relation RTEs and retrieves the necessary
|
||||||
* catalog information for each relation. Using this information, it clears
|
* catalog information for each relation. Using this information, it clears
|
||||||
* the inh flag for any relation that has no children, and expands virtual
|
* the inh flag for any relation that has no children, collects not-null
|
||||||
* generated columns for any relation that contains them.
|
* attribute numbers for any relation that has column not-null constraints, and
|
||||||
|
* expands virtual generated columns for any relation that contains them.
|
||||||
*
|
*
|
||||||
* Note that expanding virtual generated columns may cause the query tree to
|
* Note that expanding virtual generated columns may cause the query tree to
|
||||||
* have new copies of rangetable entries. Therefore, we have to use list_nth
|
* have new copies of rangetable entries. Therefore, we have to use list_nth
|
||||||
@ -447,6 +449,13 @@ preprocess_relation_rtes(PlannerInfo *root)
|
|||||||
if (rte->inh)
|
if (rte->inh)
|
||||||
rte->inh = relation->rd_rel->relhassubclass;
|
rte->inh = relation->rd_rel->relhassubclass;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check to see if the relation has any column not-null constraints;
|
||||||
|
* if so, retrieve the constraint information and store it in a
|
||||||
|
* relation OID based hash table.
|
||||||
|
*/
|
||||||
|
get_relation_notnullatts(root, relation);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check to see if the relation has any virtual generated columns; if
|
* Check to see if the relation has any virtual generated columns; if
|
||||||
* so, replace all Var nodes in the query that reference these columns
|
* so, replace all Var nodes in the query that reference these columns
|
||||||
@ -1384,8 +1393,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
|
|||||||
/*
|
/*
|
||||||
* Scan the rangetable for relation RTEs and retrieve the necessary
|
* Scan the rangetable for relation RTEs and retrieve the necessary
|
||||||
* catalog information for each relation. Using this information, clear
|
* catalog information for each relation. Using this information, clear
|
||||||
* the inh flag for any relation that has no children, and expand virtual
|
* the inh flag for any relation that has no children, collect not-null
|
||||||
* generated columns for any relation that contains them.
|
* attribute numbers for any relation that has column not-null
|
||||||
|
* constraints, and expand virtual generated columns for any relation that
|
||||||
|
* contains them.
|
||||||
*/
|
*/
|
||||||
subquery = subroot->parse = preprocess_relation_rtes(subroot);
|
subquery = subroot->parse = preprocess_relation_rtes(subroot);
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "access/htup_details.h"
|
#include "access/htup_details.h"
|
||||||
|
#include "catalog/pg_class.h"
|
||||||
#include "catalog/pg_language.h"
|
#include "catalog/pg_language.h"
|
||||||
#include "catalog/pg_operator.h"
|
#include "catalog/pg_operator.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
@ -36,6 +37,7 @@
|
|||||||
#include "optimizer/clauses.h"
|
#include "optimizer/clauses.h"
|
||||||
#include "optimizer/cost.h"
|
#include "optimizer/cost.h"
|
||||||
#include "optimizer/optimizer.h"
|
#include "optimizer/optimizer.h"
|
||||||
|
#include "optimizer/pathnode.h"
|
||||||
#include "optimizer/plancat.h"
|
#include "optimizer/plancat.h"
|
||||||
#include "optimizer/planmain.h"
|
#include "optimizer/planmain.h"
|
||||||
#include "parser/analyze.h"
|
#include "parser/analyze.h"
|
||||||
@ -43,6 +45,7 @@
|
|||||||
#include "parser/parse_collate.h"
|
#include "parser/parse_collate.h"
|
||||||
#include "parser/parse_func.h"
|
#include "parser/parse_func.h"
|
||||||
#include "parser/parse_oper.h"
|
#include "parser/parse_oper.h"
|
||||||
|
#include "parser/parsetree.h"
|
||||||
#include "rewrite/rewriteHandler.h"
|
#include "rewrite/rewriteHandler.h"
|
||||||
#include "rewrite/rewriteManip.h"
|
#include "rewrite/rewriteManip.h"
|
||||||
#include "tcop/tcopprot.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.
|
* 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
|
* 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
|
* NOTE: the planner assumes that this will always flatten nested AND and
|
||||||
* OR clauses into N-argument form. See comments in prepqual.c.
|
* 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);
|
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 = makeNode(NullTest);
|
||||||
newntest->arg = (Expr *) arg;
|
newntest->arg = (Expr *) arg;
|
||||||
@ -4162,6 +4191,67 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
|
|||||||
return newexpr;
|
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
|
* expand_function_arguments: convert named-notation args to positional args
|
||||||
* and/or insert default args, as needed
|
* and/or insert default args, as needed
|
||||||
|
@ -466,8 +466,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
|
|||||||
Index *childRTindex_p)
|
Index *childRTindex_p)
|
||||||
{
|
{
|
||||||
Query *parse = root->parse;
|
Query *parse = root->parse;
|
||||||
Oid parentOID PG_USED_FOR_ASSERTS_ONLY =
|
Oid parentOID = RelationGetRelid(parentrel);
|
||||||
RelationGetRelid(parentrel);
|
|
||||||
Oid childOID = RelationGetRelid(childrel);
|
Oid childOID = RelationGetRelid(childrel);
|
||||||
RangeTblEntry *childrte;
|
RangeTblEntry *childrte;
|
||||||
Index childRTindex;
|
Index childRTindex;
|
||||||
@ -513,6 +512,13 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
|
|||||||
*childrte_p = childrte;
|
*childrte_p = childrte;
|
||||||
*childRTindex_p = childRTindex;
|
*childRTindex_p = childRTindex;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retrieve column not-null constraint information for the child relation
|
||||||
|
* if its relation OID is different from the parent's.
|
||||||
|
*/
|
||||||
|
if (childOID != parentOID)
|
||||||
|
get_relation_notnullatts(root, childrel);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build an AppendRelInfo struct for each parent/child pair.
|
* Build an AppendRelInfo struct for each parent/child pair.
|
||||||
*/
|
*/
|
||||||
|
@ -59,6 +59,12 @@ int constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
|
|||||||
/* Hook for plugins to get control in get_relation_info() */
|
/* Hook for plugins to get control in get_relation_info() */
|
||||||
get_relation_info_hook_type get_relation_info_hook = NULL;
|
get_relation_info_hook_type get_relation_info_hook = NULL;
|
||||||
|
|
||||||
|
typedef struct NotnullHashEntry
|
||||||
|
{
|
||||||
|
Oid relid; /* OID of the relation */
|
||||||
|
Relids notnullattnums; /* attnums of NOT NULL columns */
|
||||||
|
} NotnullHashEntry;
|
||||||
|
|
||||||
|
|
||||||
static void get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
|
static void get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
|
||||||
Relation relation, bool inhparent);
|
Relation relation, bool inhparent);
|
||||||
@ -172,27 +178,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
|||||||
* RangeTblEntry does get populated.
|
* RangeTblEntry does get populated.
|
||||||
*/
|
*/
|
||||||
if (!inhparent || relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
if (!inhparent || relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||||
{
|
rel->notnullattnums = find_relation_notnullatts(root, relationObjectId);
|
||||||
for (int i = 0; i < relation->rd_att->natts; i++)
|
|
||||||
{
|
|
||||||
CompactAttribute *attr = TupleDescCompactAttr(relation->rd_att, i);
|
|
||||||
|
|
||||||
Assert(attr->attnullability != ATTNULLABLE_UNKNOWN);
|
|
||||||
|
|
||||||
if (attr->attnullability == ATTNULLABLE_VALID)
|
|
||||||
{
|
|
||||||
rel->notnullattnums = bms_add_member(rel->notnullattnums,
|
|
||||||
i + 1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Per RemoveAttributeById(), dropped columns will have their
|
|
||||||
* attnotnull unset, so we needn't check for dropped columns
|
|
||||||
* in the above condition.
|
|
||||||
*/
|
|
||||||
Assert(!attr->attisdropped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Estimate relation size --- unless it's an inheritance parent, in which
|
* Estimate relation size --- unless it's an inheritance parent, in which
|
||||||
@ -683,6 +669,105 @@ get_relation_foreign_keys(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_relation_notnullatts -
|
||||||
|
* Retrieves column not-null constraint information for a given relation.
|
||||||
|
*
|
||||||
|
* We do this while we have the relcache entry open, and store the column
|
||||||
|
* not-null constraint information in a hash table based on the relation OID.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
get_relation_notnullatts(PlannerInfo *root, Relation relation)
|
||||||
|
{
|
||||||
|
Oid relid = RelationGetRelid(relation);
|
||||||
|
NotnullHashEntry *hentry;
|
||||||
|
bool found;
|
||||||
|
Relids notnullattnums = NULL;
|
||||||
|
|
||||||
|
/* bail out if the relation has no not-null constraints */
|
||||||
|
if (relation->rd_att->constr == NULL ||
|
||||||
|
!relation->rd_att->constr->has_not_null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* create the hash table if it hasn't been created yet */
|
||||||
|
if (root->glob->rel_notnullatts_hash == NULL)
|
||||||
|
{
|
||||||
|
HTAB *hashtab;
|
||||||
|
HASHCTL hash_ctl;
|
||||||
|
|
||||||
|
hash_ctl.keysize = sizeof(Oid);
|
||||||
|
hash_ctl.entrysize = sizeof(NotnullHashEntry);
|
||||||
|
hash_ctl.hcxt = CurrentMemoryContext;
|
||||||
|
|
||||||
|
hashtab = hash_create("Relation NOT NULL attnums",
|
||||||
|
64L, /* arbitrary initial size */
|
||||||
|
&hash_ctl,
|
||||||
|
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
|
||||||
|
|
||||||
|
root->glob->rel_notnullatts_hash = hashtab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a hash entry for this relation OID, if we don't have one
|
||||||
|
* already.
|
||||||
|
*/
|
||||||
|
hentry = (NotnullHashEntry *) hash_search(root->glob->rel_notnullatts_hash,
|
||||||
|
&relid,
|
||||||
|
HASH_ENTER,
|
||||||
|
&found);
|
||||||
|
|
||||||
|
/* bail out if a hash entry already exists for this relation OID */
|
||||||
|
if (found)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* collect the column not-null constraint information for this relation */
|
||||||
|
for (int i = 0; i < relation->rd_att->natts; i++)
|
||||||
|
{
|
||||||
|
CompactAttribute *attr = TupleDescCompactAttr(relation->rd_att, i);
|
||||||
|
|
||||||
|
Assert(attr->attnullability != ATTNULLABLE_UNKNOWN);
|
||||||
|
|
||||||
|
if (attr->attnullability == ATTNULLABLE_VALID)
|
||||||
|
{
|
||||||
|
notnullattnums = bms_add_member(notnullattnums, i + 1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Per RemoveAttributeById(), dropped columns will have their
|
||||||
|
* attnotnull unset, so we needn't check for dropped columns in
|
||||||
|
* the above condition.
|
||||||
|
*/
|
||||||
|
Assert(!attr->attisdropped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ... and initialize the new hash entry */
|
||||||
|
hentry->notnullattnums = notnullattnums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* find_relation_notnullatts -
|
||||||
|
* Searches the hash table and returns the column not-null constraint
|
||||||
|
* information for a given relation.
|
||||||
|
*/
|
||||||
|
Relids
|
||||||
|
find_relation_notnullatts(PlannerInfo *root, Oid relid)
|
||||||
|
{
|
||||||
|
NotnullHashEntry *hentry;
|
||||||
|
bool found;
|
||||||
|
|
||||||
|
if (root->glob->rel_notnullatts_hash == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
hentry = (NotnullHashEntry *) hash_search(root->glob->rel_notnullatts_hash,
|
||||||
|
&relid,
|
||||||
|
HASH_FIND,
|
||||||
|
&found);
|
||||||
|
if (!found)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return hentry->notnullattnums;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* infer_arbiter_indexes -
|
* infer_arbiter_indexes -
|
||||||
* Determine the unique indexes used to arbitrate speculative insertion.
|
* Determine the unique indexes used to arbitrate speculative insertion.
|
||||||
|
@ -179,6 +179,9 @@ typedef struct PlannerGlobal
|
|||||||
|
|
||||||
/* partition descriptors */
|
/* partition descriptors */
|
||||||
PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
|
PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
|
||||||
|
|
||||||
|
/* hash table for NOT NULL attnums of relations */
|
||||||
|
struct HTAB *rel_notnullatts_hash pg_node_attr(read_write_ignore);
|
||||||
} PlannerGlobal;
|
} PlannerGlobal;
|
||||||
|
|
||||||
/* macro for fetching the Plan associated with a SubPlan node */
|
/* macro for fetching the Plan associated with a SubPlan node */
|
||||||
@ -719,6 +722,9 @@ typedef struct PartitionSchemeData *PartitionScheme;
|
|||||||
* the attribute is needed as part of final targetlist
|
* the attribute is needed as part of final targetlist
|
||||||
* attr_widths - cache space for per-attribute width estimates;
|
* attr_widths - cache space for per-attribute width estimates;
|
||||||
* zero means not computed yet
|
* zero means not computed yet
|
||||||
|
* notnullattnums - zero-based set containing attnums of NOT NULL
|
||||||
|
* columns (not populated for rels corresponding to
|
||||||
|
* non-partitioned inh==true RTEs)
|
||||||
* nulling_relids - relids of outer joins that can null this rel
|
* nulling_relids - relids of outer joins that can null this rel
|
||||||
* lateral_vars - lateral cross-references of rel, if any (list of
|
* lateral_vars - lateral cross-references of rel, if any (list of
|
||||||
* Vars and PlaceHolderVars)
|
* Vars and PlaceHolderVars)
|
||||||
@ -952,11 +958,7 @@ typedef struct RelOptInfo
|
|||||||
Relids *attr_needed pg_node_attr(read_write_ignore);
|
Relids *attr_needed pg_node_attr(read_write_ignore);
|
||||||
/* array indexed [min_attr .. max_attr] */
|
/* array indexed [min_attr .. max_attr] */
|
||||||
int32 *attr_widths pg_node_attr(read_write_ignore);
|
int32 *attr_widths pg_node_attr(read_write_ignore);
|
||||||
|
/* zero-based set containing attnums of NOT NULL columns */
|
||||||
/*
|
|
||||||
* Zero-based set containing attnums of NOT NULL columns. Not populated
|
|
||||||
* for rels corresponding to non-partitioned inh==true RTEs.
|
|
||||||
*/
|
|
||||||
Bitmapset *notnullattnums;
|
Bitmapset *notnullattnums;
|
||||||
/* relids of outer joins that can null this baserel */
|
/* relids of outer joins that can null this baserel */
|
||||||
Relids nulling_relids;
|
Relids nulling_relids;
|
||||||
|
@ -154,6 +154,8 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
|
|||||||
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
|
extern Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
|
||||||
Oid result_collation);
|
Oid result_collation);
|
||||||
|
|
||||||
|
extern bool var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info);
|
||||||
|
|
||||||
extern List *expand_function_arguments(List *args, bool include_out_arguments,
|
extern List *expand_function_arguments(List *args, bool include_out_arguments,
|
||||||
Oid result_type,
|
Oid result_type,
|
||||||
struct HeapTupleData *func_tuple);
|
struct HeapTupleData *func_tuple);
|
||||||
|
@ -28,6 +28,10 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
|
|||||||
extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
|
extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
|
||||||
bool inhparent, RelOptInfo *rel);
|
bool inhparent, RelOptInfo *rel);
|
||||||
|
|
||||||
|
extern void get_relation_notnullatts(PlannerInfo *root, Relation relation);
|
||||||
|
|
||||||
|
extern Relids find_relation_notnullatts(PlannerInfo *root, Oid relid);
|
||||||
|
|
||||||
extern List *infer_arbiter_indexes(PlannerInfo *root);
|
extern List *infer_arbiter_indexes(PlannerInfo *root);
|
||||||
|
|
||||||
extern void estimate_rel_size(Relation rel, int32 *attr_widths,
|
extern void estimate_rel_size(Relation rel, int32 *attr_widths,
|
||||||
|
@ -1551,10 +1551,10 @@ explain (costs off)
|
|||||||
select t1.a from gtest32 t1 left join gtest32 t2 on t1.a = t2.a
|
select t1.a from gtest32 t1 left join gtest32 t2 on t1.a = t2.a
|
||||||
where coalesce(t2.b, 1) = 2 or t1.a is null;
|
where coalesce(t2.b, 1) = 2 or t1.a is null;
|
||||||
QUERY PLAN
|
QUERY PLAN
|
||||||
-------------------------------------------------------------
|
-----------------------------------------
|
||||||
Hash Left Join
|
Hash Left Join
|
||||||
Hash Cond: (t1.a = t2.a)
|
Hash Cond: (t1.a = t2.a)
|
||||||
Filter: ((COALESCE((t2.a * 2), 1) = 2) OR (t1.a IS NULL))
|
Filter: (COALESCE((t2.a * 2), 1) = 2)
|
||||||
-> Seq Scan on gtest32 t1
|
-> Seq Scan on gtest32 t1
|
||||||
-> Hash
|
-> Hash
|
||||||
-> Seq Scan on gtest32 t2
|
-> Seq Scan on gtest32 t2
|
||||||
|
@ -3640,7 +3640,7 @@ from nt3 as nt3
|
|||||||
on ss2.id = nt3.nt2_id
|
on ss2.id = nt3.nt2_id
|
||||||
where nt3.id = 1 and ss2.b3;
|
where nt3.id = 1 and ss2.b3;
|
||||||
QUERY PLAN
|
QUERY PLAN
|
||||||
-----------------------------------------------
|
----------------------------------------------
|
||||||
Nested Loop
|
Nested Loop
|
||||||
-> Nested Loop
|
-> Nested Loop
|
||||||
-> Index Scan using nt3_pkey on nt3
|
-> Index Scan using nt3_pkey on nt3
|
||||||
@ -3649,7 +3649,7 @@ where nt3.id = 1 and ss2.b3;
|
|||||||
Index Cond: (id = nt3.nt2_id)
|
Index Cond: (id = nt3.nt2_id)
|
||||||
-> Index Only Scan using nt1_pkey on nt1
|
-> Index Only Scan using nt1_pkey on nt1
|
||||||
Index Cond: (id = nt2.nt1_id)
|
Index Cond: (id = nt2.nt1_id)
|
||||||
Filter: (nt2.b1 AND (id IS NOT NULL))
|
Filter: (nt2.b1 AND true)
|
||||||
(9 rows)
|
(9 rows)
|
||||||
|
|
||||||
select nt3.id
|
select nt3.id
|
||||||
|
@ -85,9 +85,9 @@ SELECT * FROM pred_tab t WHERE t.a IS NULL OR t.c IS NULL;
|
|||||||
EXPLAIN (COSTS OFF)
|
EXPLAIN (COSTS OFF)
|
||||||
SELECT * FROM pred_tab t WHERE t.b IS NULL OR t.c IS NULL;
|
SELECT * FROM pred_tab t WHERE t.b IS NULL OR t.c IS NULL;
|
||||||
QUERY PLAN
|
QUERY PLAN
|
||||||
----------------------------------------
|
------------------------
|
||||||
Seq Scan on pred_tab t
|
Seq Scan on pred_tab t
|
||||||
Filter: ((b IS NULL) OR (c IS NULL))
|
Filter: (b IS NULL)
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
--
|
--
|
||||||
@ -231,6 +231,54 @@ SELECT * FROM pred_tab t1
|
|||||||
-> Seq Scan on pred_tab t3
|
-> Seq Scan on pred_tab t3
|
||||||
(9 rows)
|
(9 rows)
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Tests for NullTest reduction in EXISTS sublink
|
||||||
|
--
|
||||||
|
-- Ensure the IS_NOT_NULL qual is ignored
|
||||||
|
EXPLAIN (COSTS OFF)
|
||||||
|
SELECT * FROM pred_tab t1
|
||||||
|
LEFT JOIN pred_tab t2 ON EXISTS
|
||||||
|
(SELECT 1 FROM pred_tab t3, pred_tab t4, pred_tab t5, pred_tab t6
|
||||||
|
WHERE t1.a = t3.a AND t6.a IS NOT NULL);
|
||||||
|
QUERY PLAN
|
||||||
|
---------------------------------------------------------
|
||||||
|
Nested Loop Left Join
|
||||||
|
Join Filter: EXISTS(SubPlan 1)
|
||||||
|
-> Seq Scan on pred_tab t1
|
||||||
|
-> Materialize
|
||||||
|
-> Seq Scan on pred_tab t2
|
||||||
|
SubPlan 1
|
||||||
|
-> Nested Loop
|
||||||
|
-> Nested Loop
|
||||||
|
-> Nested Loop
|
||||||
|
-> Seq Scan on pred_tab t4
|
||||||
|
-> Materialize
|
||||||
|
-> Seq Scan on pred_tab t3
|
||||||
|
Filter: (t1.a = a)
|
||||||
|
-> Materialize
|
||||||
|
-> Seq Scan on pred_tab t5
|
||||||
|
-> Materialize
|
||||||
|
-> Seq Scan on pred_tab t6
|
||||||
|
(17 rows)
|
||||||
|
|
||||||
|
-- Ensure the IS_NULL qual is reduced to constant-FALSE
|
||||||
|
EXPLAIN (COSTS OFF)
|
||||||
|
SELECT * FROM pred_tab t1
|
||||||
|
LEFT JOIN pred_tab t2 ON EXISTS
|
||||||
|
(SELECT 1 FROM pred_tab t3, pred_tab t4, pred_tab t5, pred_tab t6
|
||||||
|
WHERE t1.a = t3.a AND t6.a IS NULL);
|
||||||
|
QUERY PLAN
|
||||||
|
-------------------------------------
|
||||||
|
Nested Loop Left Join
|
||||||
|
Join Filter: (InitPlan 1).col1
|
||||||
|
InitPlan 1
|
||||||
|
-> Result
|
||||||
|
One-Time Filter: false
|
||||||
|
-> Seq Scan on pred_tab t1
|
||||||
|
-> Materialize
|
||||||
|
-> Seq Scan on pred_tab t2
|
||||||
|
(8 rows)
|
||||||
|
|
||||||
DROP TABLE pred_tab;
|
DROP TABLE pred_tab;
|
||||||
-- Validate we handle IS NULL and IS NOT NULL quals correctly with inheritance
|
-- Validate we handle IS NULL and IS NOT NULL quals correctly with inheritance
|
||||||
-- parents.
|
-- parents.
|
||||||
|
@ -115,6 +115,24 @@ SELECT * FROM pred_tab t1
|
|||||||
LEFT JOIN pred_tab t2 ON t1.a = 1
|
LEFT JOIN pred_tab t2 ON t1.a = 1
|
||||||
LEFT JOIN pred_tab t3 ON t2.a IS NULL OR t2.c IS NULL;
|
LEFT JOIN pred_tab t3 ON t2.a IS NULL OR t2.c IS NULL;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Tests for NullTest reduction in EXISTS sublink
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Ensure the IS_NOT_NULL qual is ignored
|
||||||
|
EXPLAIN (COSTS OFF)
|
||||||
|
SELECT * FROM pred_tab t1
|
||||||
|
LEFT JOIN pred_tab t2 ON EXISTS
|
||||||
|
(SELECT 1 FROM pred_tab t3, pred_tab t4, pred_tab t5, pred_tab t6
|
||||||
|
WHERE t1.a = t3.a AND t6.a IS NOT NULL);
|
||||||
|
|
||||||
|
-- Ensure the IS_NULL qual is reduced to constant-FALSE
|
||||||
|
EXPLAIN (COSTS OFF)
|
||||||
|
SELECT * FROM pred_tab t1
|
||||||
|
LEFT JOIN pred_tab t2 ON EXISTS
|
||||||
|
(SELECT 1 FROM pred_tab t3, pred_tab t4, pred_tab t5, pred_tab t6
|
||||||
|
WHERE t1.a = t3.a AND t6.a IS NULL);
|
||||||
|
|
||||||
DROP TABLE pred_tab;
|
DROP TABLE pred_tab;
|
||||||
|
|
||||||
-- Validate we handle IS NULL and IS NOT NULL quals correctly with inheritance
|
-- Validate we handle IS NULL and IS NOT NULL quals correctly with inheritance
|
||||||
|
@ -1759,6 +1759,7 @@ NonEmptyRange
|
|||||||
Notification
|
Notification
|
||||||
NotificationList
|
NotificationList
|
||||||
NotifyStmt
|
NotifyStmt
|
||||||
|
NotnullHashEntry
|
||||||
Nsrt
|
Nsrt
|
||||||
NtDllRoutine
|
NtDllRoutine
|
||||||
NtFlushBuffersFileEx_t
|
NtFlushBuffersFileEx_t
|
||||||
|
Reference in New Issue
Block a user