mirror of
https://github.com/postgres/postgres.git
synced 2025-06-11 20:28:21 +03:00
Teach parser to transform "x IS [NOT] DISTINCT FROM NULL" to a NullTest.
Now that we've nailed down the principle that NullTest with !argisrow is fully equivalent to SQL's IS [NOT] DISTINCT FROM NULL, let's teach the parser about it. This produces a slightly more compact parse tree and is much more amenable to optimization than a DistinctExpr, since the planner knows a good deal about NullTest and next to nothing about DistinctExpr. I'm not sure that there are all that many queries in the wild that could be improved by this, but at least one source of such cases is the patch just made to postgres_fdw to emit IS [NOT] DISTINCT FROM NULL when IS [NOT] NULL isn't semantically correct. No back-patch, since to the extent that this does affect planning results, it might be considered undesirable plan destabilization.
This commit is contained in:
@ -124,6 +124,8 @@ static Node *make_row_distinct_op(ParseState *pstate, List *opname,
|
||||
RowExpr *lrow, RowExpr *rrow, int location);
|
||||
static Expr *make_distinct_op(ParseState *pstate, List *opname,
|
||||
Node *ltree, Node *rtree, int location);
|
||||
static Node *make_nulltest_from_distinct(ParseState *pstate,
|
||||
A_Expr *distincta, Node *arg);
|
||||
static int operator_precedence_group(Node *node, const char **nodename);
|
||||
static void emit_precedence_warnings(ParseState *pstate,
|
||||
int opgroup, const char *opname,
|
||||
@ -224,6 +226,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
|
||||
result = transformAExprOpAll(pstate, a);
|
||||
break;
|
||||
case AEXPR_DISTINCT:
|
||||
case AEXPR_NOT_DISTINCT:
|
||||
result = transformAExprDistinct(pstate, a);
|
||||
break;
|
||||
case AEXPR_NULLIF:
|
||||
@ -991,12 +994,23 @@ transformAExprDistinct(ParseState *pstate, A_Expr *a)
|
||||
{
|
||||
Node *lexpr = a->lexpr;
|
||||
Node *rexpr = a->rexpr;
|
||||
Node *result;
|
||||
|
||||
if (operator_precedence_warning)
|
||||
emit_precedence_warnings(pstate, PREC_GROUP_INFIX_IS, "IS",
|
||||
lexpr, rexpr,
|
||||
a->location);
|
||||
|
||||
/*
|
||||
* If either input is an undecorated NULL literal, transform to a NullTest
|
||||
* on the other input. That's simpler to process than a full DistinctExpr,
|
||||
* and it avoids needing to require that the datatype have an = operator.
|
||||
*/
|
||||
if (exprIsNullConstant(rexpr))
|
||||
return make_nulltest_from_distinct(pstate, a, lexpr);
|
||||
if (exprIsNullConstant(lexpr))
|
||||
return make_nulltest_from_distinct(pstate, a, rexpr);
|
||||
|
||||
lexpr = transformExprRecurse(pstate, lexpr);
|
||||
rexpr = transformExprRecurse(pstate, rexpr);
|
||||
|
||||
@ -1004,20 +1018,31 @@ transformAExprDistinct(ParseState *pstate, A_Expr *a)
|
||||
rexpr && IsA(rexpr, RowExpr))
|
||||
{
|
||||
/* ROW() op ROW() is handled specially */
|
||||
return make_row_distinct_op(pstate, a->name,
|
||||
(RowExpr *) lexpr,
|
||||
(RowExpr *) rexpr,
|
||||
a->location);
|
||||
result = make_row_distinct_op(pstate, a->name,
|
||||
(RowExpr *) lexpr,
|
||||
(RowExpr *) rexpr,
|
||||
a->location);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Ordinary scalar operator */
|
||||
return (Node *) make_distinct_op(pstate,
|
||||
a->name,
|
||||
lexpr,
|
||||
rexpr,
|
||||
a->location);
|
||||
result = (Node *) make_distinct_op(pstate,
|
||||
a->name,
|
||||
lexpr,
|
||||
rexpr,
|
||||
a->location);
|
||||
}
|
||||
|
||||
/*
|
||||
* If it's NOT DISTINCT, we first build a DistinctExpr and then stick a
|
||||
* NOT on top.
|
||||
*/
|
||||
if (a->kind == AEXPR_NOT_DISTINCT)
|
||||
result = (Node *) makeBoolExpr(NOT_EXPR,
|
||||
list_make1(result),
|
||||
a->location);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Node *
|
||||
@ -2869,6 +2894,28 @@ make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Produce a NullTest node from an IS [NOT] DISTINCT FROM NULL construct
|
||||
*
|
||||
* "arg" is the untransformed other argument
|
||||
*/
|
||||
static Node *
|
||||
make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg)
|
||||
{
|
||||
NullTest *nt = makeNode(NullTest);
|
||||
|
||||
nt->arg = (Expr *) transformExprRecurse(pstate, arg);
|
||||
/* the argument can be any type, so don't coerce it */
|
||||
if (distincta->kind == AEXPR_NOT_DISTINCT)
|
||||
nt->nulltesttype = IS_NULL;
|
||||
else
|
||||
nt->nulltesttype = IS_NOT_NULL;
|
||||
/* argisrow = false is correct whether or not arg is composite */
|
||||
nt->argisrow = false;
|
||||
nt->location = distincta->location;
|
||||
return (Node *) nt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Identify node's group for operator precedence warnings
|
||||
*
|
||||
@ -2971,7 +3018,8 @@ operator_precedence_group(Node *node, const char **nodename)
|
||||
*nodename = strVal(llast(aexpr->name));
|
||||
group = PREC_GROUP_POSTFIX_OP;
|
||||
}
|
||||
else if (aexpr->kind == AEXPR_DISTINCT)
|
||||
else if (aexpr->kind == AEXPR_DISTINCT ||
|
||||
aexpr->kind == AEXPR_NOT_DISTINCT)
|
||||
{
|
||||
*nodename = "IS";
|
||||
group = PREC_GROUP_INFIX_IS;
|
||||
|
Reference in New Issue
Block a user