@ -21,6 +21,7 @@
# include "access/htup_details.h"
# include "catalog/pg_class.h"
# include "catalog/pg_inherits.h"
# include "catalog/pg_language.h"
# include "catalog/pg_operator.h"
# include "catalog/pg_proc.h"
@ -112,6 +113,7 @@ static bool contain_context_dependent_node_walker(Node *node, int *flags);
static bool contain_leaked_vars_walker ( Node * node , void * context ) ;
static Relids find_nonnullable_rels_walker ( Node * node , bool top_level ) ;
static List * find_nonnullable_vars_walker ( Node * node , bool top_level ) ;
static void find_subquery_safe_quals ( Node * jtnode , List * * safe_quals ) ;
static bool is_strict_saop ( ScalarArrayOpExpr * expr , bool falseOK ) ;
static bool convert_saop_to_hashed_saop_walker ( Node * node , void * context ) ;
static Node * eval_const_expressions_mutator ( Node * node ,
@ -1433,6 +1435,10 @@ contain_leaked_vars_walker(Node *node, void *context)
context ) ;
}
/*****************************************************************************
* Nullability analysis
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* find_nonnullable_rels
* Determine which base rels are forced nonnullable by given clause .
@ -1701,7 +1707,7 @@ find_nonnullable_rels_walker(Node *node, bool top_level)
* but here we assume that the input is a Boolean expression , and wish to
* see if NULL inputs will provably cause a FALSE - or - NULL result . We expect
* the expression to have been AND / OR flattened and converted to implicit - AND
* format .
* format ( but the results are still good if it wasn ' t AND / OR flattened ) .
*
* Attnos of the identified Vars are returned in a multibitmapset ( a List of
* Bitmapsets ) . List indexes correspond to relids ( varnos ) , while the per - rel
@ -2021,6 +2027,231 @@ find_forced_null_var(Node *node)
return NULL ;
}
/*
* query_outputs_are_not_nullable
* Returns TRUE if the output values of the Query are certainly not NULL .
* All output columns must return non - NULL to answer TRUE .
*
* The reason this takes a Query , and not just an individual tlist expression ,
* is so that we can make use of the query ' s WHERE / ON clauses to prove it does
* not return nulls .
*
* In current usage , the passed sub - Query hasn ' t yet been through any planner
* processing . This means that applying find_nonnullable_vars ( ) to its WHERE
* clauses isn ' t really ideal : for lack of const - simplification , we might be
* unable to prove not - nullness in some cases where we could have proved it
* afterwards . However , we should not get any false positive results .
*
* Like the other forms of nullability analysis above , we can err on the
* side of conservatism : if we ' re not sure , it ' s okay to return FALSE .
*/
bool
query_outputs_are_not_nullable ( Query * query )
{
PlannerInfo subroot ;
List * safe_quals = NIL ;
List * nonnullable_vars = NIL ;
bool computed_nonnullable_vars = false ;
/*
* If the query contains set operations , punt . The set ops themselves
* couldn ' t introduce nulls that weren ' t in their inputs , but the tlist
* present in the top - level query is just dummy and won ' t give us useful
* info . We could get an answer by recursing to examine each leaf query ,
* but for the moment it doesn ' t seem worth the extra complication .
*/
if ( query - > setOperations )
return false ;
/*
* If the query contains grouping sets , punt . Grouping sets can introduce
* NULL values , and we currently lack the PlannerInfo needed to flatten
* grouping Vars in the query ' s outputs .
*/
if ( query - > groupingSets )
return false ;
/*
* We need a PlannerInfo to pass to expr_is_nonnullable . Fortunately , we
* can cons up an entirely dummy one , because only the " parse " link in the
* struct is used by expr_is_nonnullable .
*/
MemSet ( & subroot , 0 , sizeof ( subroot ) ) ;
subroot . parse = query ;
/*
* Examine each targetlist entry to prove that it can ' t produce NULL .
*/
foreach_node ( TargetEntry , tle , query - > targetList )
{
Expr * expr = tle - > expr ;
/* Resjunk columns can be ignored: they don't produce output values */
if ( tle - > resjunk )
continue ;
/*
* Look through binary relabelings , since we know those don ' t
* introduce nulls .
*/
while ( expr & & IsA ( expr , RelabelType ) )
expr = ( ( RelabelType * ) expr ) - > arg ;
if ( expr = = NULL ) /* paranoia */
return false ;
/*
* Since the subquery hasn ' t yet been through expression
* preprocessing , we must explicitly flatten grouping Vars and join
* alias Vars in the given expression . Note that flatten_group_exprs
* must be applied before flatten_join_alias_vars , as grouping Vars
* can wrap join alias Vars .
*
* We must also apply flatten_join_alias_vars to the quals extracted
* by find_subquery_safe_quals . We do not need to apply
* flatten_group_exprs to these quals , though , because grouping Vars
* cannot appear in jointree quals .
*/
/*
* We have verified that the query does not contain grouping sets ,
* meaning the grouping Vars will not have varnullingrels that need
* preserving , so it ' s safe to use NULL as the root here .
*/
if ( query - > hasGroupRTE )
expr = ( Expr * ) flatten_group_exprs ( NULL , query , ( Node * ) expr ) ;
/*
* We won ' t be dealing with arbitrary expressions , so it ' s safe to use
* NULL as the root , so long as adjust_standard_join_alias_expression
* can handle everything the parser would make as a join alias
* expression .
*/
expr = ( Expr * ) flatten_join_alias_vars ( NULL , query , ( Node * ) expr ) ;
/*
* Check to see if the expr cannot be NULL . Since we ' re on a raw
* parse tree , we need to look up the not - null constraints from the
* system catalogs .
*/
if ( expr_is_nonnullable ( & subroot , expr , NOTNULL_SOURCE_SYSCACHE ) )
continue ;
if ( IsA ( expr , Var ) )
{
Var * var = ( Var * ) expr ;
/*
* For a plain Var , even if that didn ' t work , we can conclude that
* the Var is not nullable if find_nonnullable_vars can find a
* " var IS NOT NULL " or similarly strict condition among the quals
* on non - outerjoined - rels . Compute the list of Vars having such
* quals if we didn ' t already .
*/
if ( ! computed_nonnullable_vars )
{
find_subquery_safe_quals ( ( Node * ) query - > jointree , & safe_quals ) ;
safe_quals = ( List * )
flatten_join_alias_vars ( NULL , query , ( Node * ) safe_quals ) ;
nonnullable_vars = find_nonnullable_vars ( ( Node * ) safe_quals ) ;
computed_nonnullable_vars = true ;
}
if ( ! mbms_is_member ( var - > varno ,
var - > varattno - FirstLowInvalidHeapAttributeNumber ,
nonnullable_vars ) )
return false ; /* we failed to prove the Var non-null */
}
else
{
/* Punt otherwise */
return false ;
}
}
return true ;
}
/*
* find_subquery_safe_quals
* Traverse jointree to locate quals on non - outerjoined - rels .
*
* We locate all WHERE and JOIN / ON quals that constrain the rels that are not
* below the nullable side of any outer join , and add them to the * safe_quals
* list ( forming a list with implicit - AND semantics ) . These quals can be used
* to prove non - nullability of the subquery ' s outputs .
*
* Top - level caller must initialize * safe_quals to NIL .
*/
static void
find_subquery_safe_quals ( Node * jtnode , List * * safe_quals )
{
if ( jtnode = = NULL )
return ;
if ( IsA ( jtnode , RangeTblRef ) )
{
/* Leaf node: nothing to do */
return ;
}
else if ( IsA ( jtnode , FromExpr ) )
{
FromExpr * f = ( FromExpr * ) jtnode ;
/* All elements of the FROM list are allowable */
foreach_ptr ( Node , child_node , f - > fromlist )
find_subquery_safe_quals ( child_node , safe_quals ) ;
/* ... and its WHERE quals are too */
if ( f - > quals )
* safe_quals = lappend ( * safe_quals , f - > quals ) ;
}
else if ( IsA ( jtnode , JoinExpr ) )
{
JoinExpr * j = ( JoinExpr * ) jtnode ;
switch ( j - > jointype )
{
case JOIN_INNER :
/* visit both children */
find_subquery_safe_quals ( j - > larg , safe_quals ) ;
find_subquery_safe_quals ( j - > rarg , safe_quals ) ;
/* and grab the ON quals too */
if ( j - > quals )
* safe_quals = lappend ( * safe_quals , j - > quals ) ;
break ;
case JOIN_LEFT :
case JOIN_SEMI :
case JOIN_ANTI :
/*
* Only the left input is possibly non - nullable ; furthermore ,
* the quals of this join don ' t constrain the left input .
* Note : we probably can ' t see SEMI or ANTI joins at this
* point , but if we do , we can treat them like LEFT joins .
*/
find_subquery_safe_quals ( j - > larg , safe_quals ) ;
break ;
case JOIN_RIGHT :
/* Reverse of the above case */
find_subquery_safe_quals ( j - > rarg , safe_quals ) ;
break ;
case JOIN_FULL :
/* Neither side is non-nullable, so stop descending */
break ;
default :
elog ( ERROR , " unrecognized join type: %d " ,
( int ) j - > jointype ) ;
break ;
}
}
else
elog ( ERROR , " unrecognized node type: %d " ,
( int ) nodeTag ( jtnode ) ) ;
}
/*
* Can we treat a ScalarArrayOpExpr as strict ?
*
@ -2739,7 +2970,8 @@ eval_const_expressions_mutator(Node *node,
if ( ! has_nullable_nonconst & &
! expr_is_nonnullable ( context - > root ,
( Expr * ) lfirst ( arg ) , false ) )
( Expr * ) lfirst ( arg ) ,
NOTNULL_SOURCE_HASHTABLE ) )
has_nullable_nonconst = true ;
}
}
@ -3418,7 +3650,8 @@ eval_const_expressions_mutator(Node *node,
newargs = lappend ( newargs , e ) ;
break ;
}
if ( expr_is_nonnullable ( context - > root , ( Expr * ) e , false ) )
if ( expr_is_nonnullable ( context - > root , ( Expr * ) e ,
NOTNULL_SOURCE_HASHTABLE ) )
{
if ( newargs = = NIL )
return e ; /* first expr */
@ -3612,7 +3845,7 @@ eval_const_expressions_mutator(Node *node,
*/
if ( relem & &
expr_is_nonnullable ( context - > root , ( Expr * ) relem ,
false ) )
NOTNULL_SOURCE_HASHTABLE ) )
{
if ( ntest - > nulltesttype = = IS_NULL )
return makeBoolConst ( false , false ) ;
@ -3664,7 +3897,8 @@ eval_const_expressions_mutator(Node *node,
return makeBoolConst ( result , false ) ;
}
if ( ! ntest - > argisrow & & arg & &
expr_is_nonnullable ( context - > root , ( Expr * ) arg , false ) )
expr_is_nonnullable ( context - > root , ( Expr * ) arg ,
NOTNULL_SOURCE_HASHTABLE ) )
{
bool result ;
@ -3749,7 +3983,9 @@ eval_const_expressions_mutator(Node *node,
return makeBoolConst ( result , false ) ;
}
if ( arg & & expr_is_nonnullable ( context - > root , ( Expr * ) arg , false ) )
if ( arg & &
expr_is_nonnullable ( context - > root , ( Expr * ) arg ,
NOTNULL_SOURCE_HASHTABLE ) )
{
/*
* If arg is proven non - nullable , simplify to boolean
@ -4384,14 +4620,11 @@ simplify_aggref(Aggref *aggref, eval_const_expressions_context *context)
* 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 .
* " source " specifies where we should look for NOT NULL proofs .
*/
bool
var_is_nonnullable ( PlannerInfo * root , Var * var , bool use_rel_info )
var_is_nonnullable ( PlannerInfo * root , Var * var , NotNullSource source )
{
Bitmapset * notnullattnums = NULL ;
Assert ( IsA ( var , Var ) ) ;
/* skip upper-level Vars */
@ -4406,35 +4639,89 @@ var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info)
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 ) ;
/* we don't trust whole-row Vars */
if ( var - > varattno = = 0 )
return false ;
notnullattnums = rel - > notnullattnums ;
}
else
/* Check if the Var is defined as NOT NULL. */
switch ( source )
{
RangeTblEntry * rte = planner_rt_fetch ( var - > varno , root ) ;
case NOTNULL_SOURCE_RELOPT :
{
/*
* We retrieve the column NOT NULL constraint information from
* the corresponding RelOptInfo .
*/
RelOptInfo * rel ;
Bitmapset * notnullattnums ;
/*
* 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 ;
rel = find_base_rel ( root , var - > varno ) ;
notnullattnums = rel - > notnullattnums ;
notnullattnums = find_relation_notnullatts ( root , rte - > relid ) ;
}
return bms_is_member ( var - > varattno , notnullattnums ) ;
}
case NOTNULL_SOURCE_HASHTABLE :
{
/*
* We retrieve the column NOT NULL constraint information from
* the hash table .
*/
RangeTblEntry * rte ;
Bitmapset * notnullattnums ;
if ( var - > varattno > 0 & &
bms_is_member ( var - > varattno , notnullattnums ) )
return true ;
rte = planner_rt_fetch ( var - > varno , root ) ;
/* We can only reason about ordinary relations */
if ( rte - > rtekind ! = RTE_RELATION )
return false ;
/*
* 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 ) ;
return bms_is_member ( var - > varattno , notnullattnums ) ;
}
case NOTNULL_SOURCE_SYSCACHE :
{
/*
* We look up the " attnotnull " field in the attribute
* relation .
*/
RangeTblEntry * rte ;
rte = planner_rt_fetch ( var - > varno , root ) ;
/* We can only reason about ordinary relations */
if ( rte - > rtekind ! = RTE_RELATION )
return false ;
/*
* 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 .
*
* Note that we need to check if the relation actually has any
* children , as we might not have done that yet .
*/
if ( rte - > inh & & has_subclass ( rte - > relid ) & &
rte - > relkind ! = RELKIND_PARTITIONED_TABLE )
return false ;
return get_attnotnull ( rte - > relid , var - > varattno ) ;
}
default :
elog ( ERROR , " unrecognized NotNullSource: %d " ,
( int ) source ) ;
break ;
}
return false ;
}
@ -4444,16 +4731,22 @@ var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info)
*
* Returns true iff the given ' expr ' cannot produce SQL NULLs .
*
* If ' use_rel_info ' is true , nullability of Vars is checked via the
* corresponding RelOptInfo for the given Var . Some callers require
* nullability information before RelOptInfos are generated . These should
* pass ' use_rel_info ' as false .
* source : specifies where we should look for NOT NULL proofs for Vars .
* - NOTNULL_SOURCE_RELOPT : Used when RelOptInfos have been generated . We
* retrieve nullability information directly from the RelOptInfo corresponding
* to the Var .
* - NOTNULL_SOURCE_HASHTABLE : Used when RelOptInfos are not yet available ,
* but we have already collected relation - level not - null constraints into the
* global hash table .
* - NOTNULL_SOURCE_SYSCACHE : Used for raw parse trees where neither
* RelOptInfos nor the hash table are available . In this case , we have to
* look up the ' attnotnull ' field directly in the system catalogs .
*
* For now , we support only a limited set of expression types . Support for
* additional node types can be added in the future .
*/
bool
expr_is_nonnullable ( PlannerInfo * root , Expr * expr , bool use_rel_info )
expr_is_nonnullable ( PlannerInfo * root , Expr * expr , NotNullSource source )
{
/* since this function recurses, it could be driven to stack overflow */
check_stack_depth ( ) ;
@ -4463,7 +4756,7 @@ expr_is_nonnullable(PlannerInfo *root, Expr *expr, bool use_rel_info)
case T_Var :
{
if ( root )
return var_is_nonnullable ( root , ( Var * ) expr , use_rel_info ) ;
return var_is_nonnullable ( root , ( Var * ) expr , source ) ;
}
break ;
case T_Const :
@ -4480,7 +4773,7 @@ expr_is_nonnullable(PlannerInfo *root, Expr *expr, bool use_rel_info)
foreach_ptr ( Expr , arg , coalesceexpr - > args )
{
if ( expr_is_nonnullable ( root , arg , use_rel_info ) )
if ( expr_is_nonnullable ( root , arg , source ) )
return true ;
}
}
@ -4495,7 +4788,7 @@ expr_is_nonnullable(PlannerInfo *root, Expr *expr, bool use_rel_info)
foreach_ptr ( Expr , arg , minmaxexpr - > args )
{
if ( expr_is_nonnullable ( root , arg , use_rel_info ) )
if ( expr_is_nonnullable ( root , arg , source ) )
return true ;
}
}
@ -4511,13 +4804,13 @@ expr_is_nonnullable(PlannerInfo *root, Expr *expr, bool use_rel_info)
/* The default result must be present and non-nullable */
if ( caseexpr - > defresult = = NULL | |
! expr_is_nonnullable ( root , caseexpr - > defresult , use_rel_info ) )
! expr_is_nonnullable ( root , caseexpr - > defresult , source ) )
return false ;
/* All branch results must be non-nullable */
foreach_ptr ( CaseWhen , casewhen , caseexpr - > args )
{
if ( ! expr_is_nonnullable ( root , casewhen - > result , use_rel_info ) )
if ( ! expr_is_nonnullable ( root , casewhen - > result , source ) )
return false ;
}
@ -4565,7 +4858,7 @@ expr_is_nonnullable(PlannerInfo *root, Expr *expr, bool use_rel_info)
* non - nullable .
*/
return expr_is_nonnullable ( root , ( ( RelabelType * ) expr ) - > arg ,
use_rel_info ) ;
source ) ;
}
default :
break ;