Skip to content

Commit 9581103

Browse files
committed
Improve planner to drop constant-NULL inputs of AND/OR where it's legal.
In general we can't discard constant-NULL inputs, since they could change the result of the AND/OR to be NULL. But at top level of WHERE, we do not need to distinguish a NULL result from a FALSE result, so it's okay to treat NULL as FALSE and then simplify AND/OR accordingly. This is a very ancient oversight, but in 9.2 and later it can lead to failure to optimize queries that previous releases did optimize, as a result of more aggressive parameter substitution rules making it possible to reduce more subexpressions to NULL constants. This is the root cause of bug #10171 from Arnold Scheffler. We could alternatively have fixed that by teaching orclauses.c to ignore constant-NULL OR arms, but it seems better to get rid of them globally. I resisted the temptation to back-patch this change into all active branches, but it seems appropriate to back-patch as far as 9.2 so that there will not be performance regressions of the kind shown in this bug.
1 parent dbe3161 commit 9581103

File tree

3 files changed

+85
-12
lines changed

3 files changed

+85
-12
lines changed

src/backend/optimizer/prep/prepqual.c

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ canonicalize_qual(Expr *qual)
293293
/*
294294
* Pull up redundant subclauses in OR-of-AND trees. We do this only
295295
* within the top-level AND/OR structure; there's no point in looking
296-
* deeper.
296+
* deeper. Also remove any NULL constants in the top-level structure.
297297
*/
298298
newqual = find_duplicate_ors(qual);
299299

@@ -393,6 +393,13 @@ pull_ors(List *orlist)
393393
* OR clauses to which the inverse OR distributive law might apply.
394394
* Only the top-level AND/OR structure is searched.
395395
*
396+
* While at it, we remove any NULL constants within the top-level AND/OR
397+
* structure, eg "x OR NULL::boolean" is reduced to "x". In general that
398+
* would change the result, so eval_const_expressions can't do it; but at
399+
* top level of WHERE, we don't need to distinguish between FALSE and NULL
400+
* results, so it's valid to treat NULL::boolean the same as FALSE and then
401+
* simplify AND/OR accordingly.
402+
*
396403
* Returns the modified qualification. AND/OR flatness is preserved.
397404
*/
398405
static Expr *
@@ -405,12 +412,30 @@ find_duplicate_ors(Expr *qual)
405412

406413
/* Recurse */
407414
foreach(temp, ((BoolExpr *) qual)->args)
408-
orlist = lappend(orlist, find_duplicate_ors(lfirst(temp)));
415+
{
416+
Expr *arg = (Expr *) lfirst(temp);
409417

410-
/*
411-
* Don't need pull_ors() since this routine will never introduce an OR
412-
* where there wasn't one before.
413-
*/
418+
arg = find_duplicate_ors(arg);
419+
420+
/* Get rid of any constant inputs */
421+
if (arg && IsA(arg, Const))
422+
{
423+
Const *carg = (Const *) arg;
424+
425+
/* Drop constant FALSE or NULL */
426+
if (carg->constisnull || !DatumGetBool(carg->constvalue))
427+
continue;
428+
/* constant TRUE, so OR reduces to TRUE */
429+
return arg;
430+
}
431+
432+
orlist = lappend(orlist, arg);
433+
}
434+
435+
/* Flatten any ORs pulled up to just below here */
436+
orlist = pull_ors(orlist);
437+
438+
/* Now we can look for duplicate ORs */
414439
return process_duplicate_ors(orlist);
415440
}
416441
else if (and_clause((Node *) qual))
@@ -420,10 +445,38 @@ find_duplicate_ors(Expr *qual)
420445

421446
/* Recurse */
422447
foreach(temp, ((BoolExpr *) qual)->args)
423-
andlist = lappend(andlist, find_duplicate_ors(lfirst(temp)));
448+
{
449+
Expr *arg = (Expr *) lfirst(temp);
450+
451+
arg = find_duplicate_ors(arg);
452+
453+
/* Get rid of any constant inputs */
454+
if (arg && IsA(arg, Const))
455+
{
456+
Const *carg = (Const *) arg;
457+
458+
/* Drop constant TRUE */
459+
if (!carg->constisnull && DatumGetBool(carg->constvalue))
460+
continue;
461+
/* constant FALSE or NULL, so AND reduces to FALSE */
462+
return (Expr *) makeBoolConst(false, false);
463+
}
464+
465+
andlist = lappend(andlist, arg);
466+
}
467+
424468
/* Flatten any ANDs introduced just below here */
425469
andlist = pull_ands(andlist);
426-
/* The AND list can't get shorter, so result is always an AND */
470+
471+
/* AND of no inputs reduces to TRUE */
472+
if (andlist == NIL)
473+
return (Expr *) makeBoolConst(true, false);
474+
475+
/* Single-expression AND just reduces to that expression */
476+
if (list_length(andlist) == 1)
477+
return (Expr *) linitial(andlist);
478+
479+
/* Else we still need an AND node */
427480
return make_andclause(andlist);
428481
}
429482
else
@@ -447,11 +500,13 @@ process_duplicate_ors(List *orlist)
447500
List *neworlist;
448501
ListCell *temp;
449502

503+
/* OR of no inputs reduces to FALSE */
450504
if (orlist == NIL)
451-
return NULL; /* probably can't happen */
452-
if (list_length(orlist) == 1) /* single-expression OR (can this
453-
* happen?) */
454-
return linitial(orlist);
505+
return (Expr *) makeBoolConst(false, false);
506+
507+
/* Single-expression OR just reduces to that expression */
508+
if (list_length(orlist) == 1)
509+
return (Expr *) linitial(orlist);
455510

456511
/*
457512
* Choose the shortest AND clause as the reference list --- obviously, any

src/test/regress/expected/create_index.out

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2771,3 +2771,14 @@ ORDER BY thousand;
27712771
1 | 1001
27722772
(2 rows)
27732773

2774+
--
2775+
-- Check elimination of constant-NULL subexpressions
2776+
--
2777+
explain (costs off)
2778+
select * from tenk1 where (thousand, tenthous) in ((1,1001), (null,null));
2779+
QUERY PLAN
2780+
------------------------------------------------------
2781+
Index Scan using tenk1_thous_tenthous on tenk1
2782+
Index Cond: ((thousand = 1) AND (tenthous = 1001))
2783+
(2 rows)
2784+

src/test/regress/sql/create_index.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,3 +931,10 @@ ORDER BY thousand;
931931
SELECT thousand, tenthous FROM tenk1
932932
WHERE thousand < 2 AND tenthous IN (1001,3000)
933933
ORDER BY thousand;
934+
935+
--
936+
-- Check elimination of constant-NULL subexpressions
937+
--
938+
939+
explain (costs off)
940+
select * from tenk1 where (thousand, tenthous) in ((1,1001), (null,null));

0 commit comments

Comments
 (0)