From: Tom Lane Date: Sun, 16 Feb 2003 02:30:39 +0000 (+0000) Subject: COALESCE() and NULLIF() are now first-class expressions, not macros X-Git-Tag: REL7_4_BETA1~1035 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=51972a9d5d068dd34b24ff4923981ffb90e5cc2d;p=postgresql COALESCE() and NULLIF() are now first-class expressions, not macros that turn into CASE expressions. They evaluate their arguments at most once. Patch by Kris Jurka, review and (very light) editorializing by me. --- diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index f2d84a969a..fd247be28c 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,5 +1,5 @@ @@ -6295,17 +6295,6 @@ SELECT NULLIF(value, '(none)') ... - - - COALESCE and NULLIF are - just shorthand for CASE expressions. They are actually - converted into CASE expressions at a very early stage - of processing, and subsequent processing thinks it is dealing with - CASE. Thus an incorrect COALESCE or - NULLIF usage may draw an error message that - refers to CASE. - - diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index f406a574c9..35df2eae26 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/dependency.c,v 1.21 2003/02/09 06:56:26 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/dependency.c,v 1.22 2003/02/16 02:30:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -933,6 +933,14 @@ find_expr_references_walker(Node *node, &context->addrs); /* fall through to examine arguments */ } + if (IsA(node, NullIfExpr)) + { + NullIfExpr *nullifexpr = (NullIfExpr *) node; + + add_object_address(OCLASS_OPERATOR, nullifexpr->opno, 0, + &context->addrs); + /* fall through to examine arguments */ + } if (IsA(node, Aggref)) { Aggref *aggref = (Aggref *) node; diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index a2583fcc4c..968617c39a 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.124 2003/02/03 21:15:43 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.125 2003/02/16 02:30:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -64,7 +64,7 @@ static Datum ExecEvalFunc(FuncExprState *fcache, ExprContext *econtext, static Datum ExecEvalOper(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalDistinct(FuncExprState *fcache, ExprContext *econtext, - bool *isNull, ExprDoneCond *isDone); + bool *isNull); static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo, List *argList, ExprContext *econtext); static Datum ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, @@ -75,6 +75,11 @@ static Datum ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, bool *isNull); static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr, + ExprContext *econtext, + bool *isNull); +static Datum ExecEvalNullIf(FuncExprState *nullIfExpr, ExprContext *econtext, + bool *isNull); static Datum ExecEvalNullTest(GenericExprState *nstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -1187,8 +1192,7 @@ ExecEvalOper(FuncExprState *fcache, static Datum ExecEvalDistinct(FuncExprState *fcache, ExprContext *econtext, - bool *isNull, - ExprDoneCond *isDone) + bool *isNull) { Datum result; FunctionCallInfoData fcinfo; @@ -1370,6 +1374,7 @@ ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, bool *isNull) return BoolGetDatum(!AnyNull); } + /* ---------------------------------------------------------------- * ExecEvalCase * @@ -1429,6 +1434,91 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, return (Datum) 0; } +/* ---------------------------------------------------------------- + * ExecEvalCoalesce + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, + bool *isNull) +{ + List *arg; + + /* Simply loop through until something NOT NULL is found */ + foreach(arg, coalesceExpr->args) + { + ExprState *e = (ExprState *) lfirst(arg); + Datum value; + + value = ExecEvalExpr(e, econtext, isNull, NULL); + if (!*isNull) + return value; + } + + /* Else return NULL */ + *isNull = true; + return (Datum) 0; +} + +/* ---------------------------------------------------------------- + * ExecEvalNullIf + * + * Note that this is *always* derived from the equals operator, + * but since we need special processing of the arguments + * we can not simply reuse ExecEvalOper() or ExecEvalFunc(). + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalNullIf(FuncExprState *fcache, ExprContext *econtext, + bool *isNull) +{ + Datum result; + FunctionCallInfoData fcinfo; + ExprDoneCond argDone; + List *argList; + + /* + * Initialize function cache if first time through + */ + if (fcache->func.fn_oid == InvalidOid) + { + NullIfExpr *op = (NullIfExpr *) fcache->xprstate.expr; + + init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); + Assert(!fcache->func.fn_retset); + } + + /* + * extract info from fcache + */ + argList = fcache->args; + + /* Need to prep callinfo structure */ + MemSet(&fcinfo, 0, sizeof(fcinfo)); + fcinfo.flinfo = &(fcache->func); + argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext); + if (argDone != ExprSingleResult) + elog(ERROR, "NULLIF does not support set arguments"); + Assert(fcinfo.nargs == 2); + + /* if either argument is NULL they can't be equal */ + if (!fcinfo.argnull[0] && !fcinfo.argnull[1]) + { + fcinfo.isnull = false; + result = FunctionCallInvoke(&fcinfo); + /* if the arguments are equal return null */ + if (!fcinfo.isnull && DatumGetBool(result)) + { + *isNull = true; + return (Datum) 0; + } + } + + /* else return first argument */ + *isNull = fcinfo.argnull[0]; + return fcinfo.arg[0]; +} + /* ---------------------------------------------------------------- * ExecEvalNullTest * @@ -1778,7 +1868,7 @@ ExecEvalExpr(ExprState *expression, break; case T_DistinctExpr: retDatum = ExecEvalDistinct((FuncExprState *) expression, econtext, - isNull, isDone); + isNull); break; case T_BoolExpr: { @@ -1826,6 +1916,16 @@ ExecEvalExpr(ExprState *expression, isNull, isDone); break; + case T_CoalesceExpr: + retDatum = ExecEvalCoalesce((CoalesceExprState *) expression, + econtext, + isNull); + break; + case T_NullIfExpr: + retDatum = ExecEvalNullIf((FuncExprState *) expression, + econtext, + isNull); + break; case T_NullTest: retDatum = ExecEvalNullTest((GenericExprState *) expression, econtext, @@ -2082,6 +2182,36 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) cstate; } break; + case T_CoalesceExpr: + { + CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; + CoalesceExprState *cstate = makeNode(CoalesceExprState); + List *outlist = NIL; + List *inlist; + + foreach(inlist, coalesceexpr->args) + { + Expr *e = (Expr *) lfirst(inlist); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + cstate->args = outlist; + state = (ExprState *) cstate; + } + break; + case T_NullIfExpr: + { + NullIfExpr *nullifexpr = (NullIfExpr *) node; + FuncExprState *fstate = makeNode(FuncExprState); + + fstate->args = (List *) + ExecInitExpr((Expr *) nullifexpr->args, parent); + fstate->func.fn_oid = InvalidOid; /* not initialized */ + state = (ExprState *) fstate; + } + break; case T_NullTest: { NullTest *ntest = (NullTest *) node; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index aa7a7efcc8..2698f08478 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.243 2003/02/10 04:44:44 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.244 2003/02/16 02:30:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -785,7 +785,7 @@ _copyOpExpr(OpExpr *from) } /* - * _copyDistinctExpr + * _copyDistinctExpr (same as OpExpr) */ static DistinctExpr * _copyDistinctExpr(DistinctExpr *from) @@ -919,6 +919,37 @@ _copyCaseWhen(CaseWhen *from) return newnode; } +/* + * _copyCoalesceExpr + */ +static CoalesceExpr * +_copyCoalesceExpr(CoalesceExpr *from) +{ + CoalesceExpr *newnode = makeNode(CoalesceExpr); + + COPY_SCALAR_FIELD(coalescetype); + COPY_NODE_FIELD(args); + + return newnode; +} + +/* + * _copyNullIfExpr (same as OpExpr) + */ +static NullIfExpr * +_copyNullIfExpr(NullIfExpr *from) +{ + NullIfExpr *newnode = makeNode(NullIfExpr); + + COPY_SCALAR_FIELD(opno); + COPY_SCALAR_FIELD(opfuncid); + COPY_SCALAR_FIELD(opresulttype); + COPY_SCALAR_FIELD(opretset); + COPY_NODE_FIELD(args); + + return newnode; +} + /* * _copyNullTest */ @@ -2484,6 +2515,12 @@ copyObject(void *from) case T_CaseWhen: retval = _copyCaseWhen(from); break; + case T_CoalesceExpr: + retval = _copyCoalesceExpr(from); + break; + case T_NullIfExpr: + retval = _copyNullIfExpr(from); + break; case T_NullTest: retval = _copyNullTest(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index c0bd77756a..378d8e4403 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.186 2003/02/10 04:44:45 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.187 2003/02/16 02:30:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -378,6 +378,37 @@ _equalCaseWhen(CaseWhen *a, CaseWhen *b) return true; } +static bool +_equalCoalesceExpr(CoalesceExpr *a, CoalesceExpr *b) +{ + COMPARE_SCALAR_FIELD(coalescetype); + COMPARE_NODE_FIELD(args); + + return true; +} + +static bool +_equalNullIfExpr(NullIfExpr *a, NullIfExpr *b) +{ + COMPARE_SCALAR_FIELD(opno); + /* + * Special-case opfuncid: it is allowable for it to differ if one + * node contains zero and the other doesn't. This just means that the + * one node isn't as far along in the parse/plan pipeline and hasn't + * had the opfuncid cache filled yet. + */ + if (a->opfuncid != b->opfuncid && + a->opfuncid != 0 && + b->opfuncid != 0) + return false; + + COMPARE_SCALAR_FIELD(opresulttype); + COMPARE_SCALAR_FIELD(opretset); + COMPARE_NODE_FIELD(args); + + return true; +} + static bool _equalNullTest(NullTest *a, NullTest *b) { @@ -1613,6 +1644,12 @@ equal(void *a, void *b) case T_CaseWhen: retval = _equalCaseWhen(a, b); break; + case T_CoalesceExpr: + retval = _equalCoalesceExpr(a, b); + break; + case T_NullIfExpr: + retval = _equalNullIfExpr(a, b); + break; case T_NullTest: retval = _equalNullTest(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 134ee4328e..8485244492 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.199 2003/02/10 04:44:45 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.200 2003/02/16 02:30:37 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -753,6 +753,27 @@ _outCaseWhen(StringInfo str, CaseWhen *node) WRITE_NODE_FIELD(result); } +static void +_outCoalesceExpr(StringInfo str, CoalesceExpr *node) +{ + WRITE_NODE_TYPE("COALESCE"); + + WRITE_OID_FIELD(coalescetype); + WRITE_NODE_FIELD(args); +} + +static void +_outNullIfExpr(StringInfo str, NullIfExpr *node) +{ + WRITE_NODE_TYPE("NULLIFEXPR"); + + WRITE_OID_FIELD(opno); + WRITE_OID_FIELD(opfuncid); + WRITE_OID_FIELD(opresulttype); + WRITE_BOOL_FIELD(opretset); + WRITE_NODE_FIELD(args); +} + static void _outNullTest(StringInfo str, NullTest *node) { @@ -1277,6 +1298,10 @@ _outAExpr(StringInfo str, A_Expr *node) appendStringInfo(str, " DISTINCT "); WRITE_NODE_FIELD(name); break; + case AEXPR_NULLIF: + appendStringInfo(str, " NULLIF "); + WRITE_NODE_FIELD(name); + break; case AEXPR_OF: appendStringInfo(str, " OF "); WRITE_NODE_FIELD(name); @@ -1576,6 +1601,12 @@ _outNode(StringInfo str, void *obj) case T_CaseWhen: _outCaseWhen(str, obj); break; + case T_CoalesceExpr: + _outCoalesceExpr(str, obj); + break; + case T_NullIfExpr: + _outNullIfExpr(str, obj); + break; case T_NullTest: _outNullTest(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index f37856728b..410d092c91 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.148 2003/02/09 06:56:27 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.149 2003/02/16 02:30:37 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -606,6 +606,47 @@ _readCaseWhen(void) READ_DONE(); } +/* + * _readCoalesceExpr + */ +static CoalesceExpr * +_readCoalesceExpr(void) +{ + READ_LOCALS(CoalesceExpr); + + READ_OID_FIELD(coalescetype); + READ_NODE_FIELD(args); + + READ_DONE(); +} + +/* + * _readNullIfExpr + */ +static NullIfExpr * +_readNullIfExpr(void) +{ + READ_LOCALS(NullIfExpr); + + READ_OID_FIELD(opno); + READ_OID_FIELD(opfuncid); + /* + * The opfuncid is stored in the textual format primarily for debugging + * and documentation reasons. We want to always read it as zero to force + * it to be re-looked-up in the pg_operator entry. This ensures that + * stored rules don't have hidden dependencies on operators' functions. + * (We don't currently support an ALTER OPERATOR command, but might + * someday.) + */ + local_node->opfuncid = InvalidOid; + + READ_OID_FIELD(opresulttype); + READ_BOOL_FIELD(opretset); + READ_NODE_FIELD(args); + + READ_DONE(); +} + /* * _readNullTest */ @@ -895,6 +936,10 @@ parseNodeString(void) return_value = _readCaseExpr(); else if (MATCH("WHEN", 4)) return_value = _readCaseWhen(); + else if (MATCH("COALESCE", 8)) + return_value = _readCoalesceExpr(); + else if (MATCH("NULLIFEXPR", 10)) + return_value = _readNullIfExpr(); else if (MATCH("NULLTEST", 8)) return_value = _readNullTest(); else if (MATCH("BOOLEANTEST", 11)) diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 54e47e2424..21bc152ce6 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -49,7 +49,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.106 2003/02/15 21:39:58 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.107 2003/02/16 02:30:38 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1468,7 +1468,8 @@ cost_qual_eval_walker(Node *node, QualCost *total) */ if (IsA(node, FuncExpr) || IsA(node, OpExpr) || - IsA(node, DistinctExpr)) + IsA(node, DistinctExpr) || + IsA(node, NullIfExpr)) { total->per_tuple += cpu_operator_cost; } diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 123b96f188..4e17a85eb4 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.91 2003/01/20 18:54:52 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.92 2003/02/16 02:30:38 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -284,6 +284,8 @@ fix_expr_references_walker(Node *node, void *context) set_opfuncid((OpExpr *) node); else if (IsA(node, DistinctExpr)) set_opfuncid((OpExpr *) node); /* rely on struct equivalence */ + else if (IsA(node, NullIfExpr)) + set_opfuncid((OpExpr *) node); /* rely on struct equivalence */ else if (IsA(node, SubPlan)) { SubPlan *sp = (SubPlan *) node; @@ -736,5 +738,7 @@ fix_opfuncids_walker(Node *node, void *context) set_opfuncid((OpExpr *) node); else if (IsA(node, DistinctExpr)) set_opfuncid((OpExpr *) node); /* rely on struct equivalence */ + else if (IsA(node, NullIfExpr)) + set_opfuncid((OpExpr *) node); /* rely on struct equivalence */ return expression_tree_walker(node, fix_opfuncids_walker, context); } diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 71b5909d20..40e440a375 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.129 2003/02/09 06:56:27 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.130 2003/02/16 02:30:38 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -451,24 +451,22 @@ expression_returns_set_walker(Node *node, void *context) return true; /* else fall through to check args */ } - if (IsA(node, DistinctExpr)) - { - DistinctExpr *expr = (DistinctExpr *) node; - - if (expr->opretset) - return true; - /* else fall through to check args */ - } /* Avoid recursion for some cases that can't return a set */ - if (IsA(node, BoolExpr)) - return false; if (IsA(node, Aggref)) return false; + if (IsA(node, DistinctExpr)) + return false; + if (IsA(node, BoolExpr)) + return false; if (IsA(node, SubLink)) return false; if (IsA(node, SubPlan)) return false; + if (IsA(node, CoalesceExpr)) + return false; + if (IsA(node, NullIfExpr)) + return false; return expression_tree_walker(node, expression_returns_set_walker, context); @@ -559,6 +557,14 @@ contain_mutable_functions_walker(Node *node, void *context) return true; /* else fall through to check args */ } + if (IsA(node, NullIfExpr)) + { + NullIfExpr *expr = (NullIfExpr *) node; + + if (op_volatile(expr->opno) != PROVOLATILE_IMMUTABLE) + return true; + /* else fall through to check args */ + } if (IsA(node, SubLink)) { SubLink *sublink = (SubLink *) node; @@ -626,6 +632,14 @@ contain_volatile_functions_walker(Node *node, void *context) return true; /* else fall through to check args */ } + if (IsA(node, NullIfExpr)) + { + NullIfExpr *expr = (NullIfExpr *) node; + + if (op_volatile(expr->opno) == PROVOLATILE_VOLATILE) + return true; + /* else fall through to check args */ + } if (IsA(node, SubLink)) { SubLink *sublink = (SubLink *) node; @@ -707,6 +721,10 @@ contain_nonstrict_functions_walker(Node *node, void *context) } if (IsA(node, CaseExpr)) return true; + if (IsA(node, CoalesceExpr)) + return true; + if (IsA(node, NullIfExpr)) + return true; if (IsA(node, NullTest)) return true; if (IsA(node, BooleanTest)) @@ -1446,6 +1464,39 @@ eval_const_expressions_mutator(Node *node, List *active_fns) newcase->defresult = (Expr *) defresult; return (Node *) newcase; } + if (IsA(node, CoalesceExpr)) + { + CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; + CoalesceExpr *newcoalesce; + List *newargs = NIL; + List *arg; + + foreach(arg, coalesceexpr->args) + { + Node *e; + + e = eval_const_expressions_mutator((Node *) lfirst(arg), + active_fns); + /* + * We can remove null constants from the list. + * For a non-null constant, if it has not been preceded by any + * other non-null-constant expressions then that is the result. + */ + if (IsA(e, Const)) + { + if (((Const *) e)->constisnull) + continue; /* drop null constant */ + if (newargs == NIL) + return e; /* first expr */ + } + newargs = lappend(newargs, e); + } + + newcoalesce = makeNode(CoalesceExpr); + newcoalesce->coalescetype = coalesceexpr->coalescetype; + newcoalesce->args = newargs; + return (Node *) newcoalesce; + } /* * For any node type not handled above, we recurse using @@ -2109,6 +2160,10 @@ expression_tree_walker(Node *node, return true; } break; + case T_CoalesceExpr: + return walker(((CoalesceExpr *) node)->args, context); + case T_NullIfExpr: + return walker(((NullIfExpr *) node)->args, context); case T_NullTest: return walker(((NullTest *) node)->arg, context); case T_BooleanTest: @@ -2481,6 +2536,26 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_CoalesceExpr: + { + CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; + CoalesceExpr *newnode; + + FLATCOPY(newnode, coalesceexpr, CoalesceExpr); + MUTATE(newnode->args, coalesceexpr->args, List *); + return (Node *) newnode; + } + break; + case T_NullIfExpr: + { + NullIfExpr *expr = (NullIfExpr *) node; + NullIfExpr *newnode; + + FLATCOPY(newnode, expr, NullIfExpr); + MUTATE(newnode->args, expr->args, List *); + return (Node *) newnode; + } + break; case T_NullTest: { NullTest *ntest = (NullTest *) node; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 5a7dff919d..c593196dfc 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.403 2003/02/13 05:25:24 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.404 2003/02/16 02:30:38 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -6650,6 +6650,10 @@ in_expr: select_with_parens * COALESCE(a,b,...) * same as CASE WHEN a IS NOT NULL THEN a WHEN b IS NOT NULL THEN b ... END * - thomas 1998-11-09 + * + * NULLIF and COALESCE have become first class nodes to + * prevent double evaluation of arguments. + * - Kris Jurka 2003-02-11 */ case_expr: CASE case_arg when_clause_list case_default END_P { @@ -6661,29 +6665,12 @@ case_expr: CASE case_arg when_clause_list case_default END_P } | NULLIF '(' a_expr ',' a_expr ')' { - CaseExpr *c = makeNode(CaseExpr); - CaseWhen *w = makeNode(CaseWhen); - - w->expr = (Expr *) makeSimpleA_Expr(AEXPR_OP, "=", $3, $5); - /* w->result is left NULL */ - c->args = makeList1(w); - c->defresult = (Expr *) $3; - $$ = (Node *)c; + $$ = (Node *) makeSimpleA_Expr(AEXPR_NULLIF, "=", $3, $5); } | COALESCE '(' expr_list ')' { - CaseExpr *c = makeNode(CaseExpr); - List *l; - foreach (l,$3) - { - CaseWhen *w = makeNode(CaseWhen); - NullTest *n = makeNode(NullTest); - n->arg = lfirst(l); - n->nulltesttype = IS_NOT_NULL; - w->expr = (Expr *) n; - w->result = lfirst(l); - c->args = lappend(c->args, w); - } + CoalesceExpr *c = makeNode(CoalesceExpr); + c->args = $3; $$ = (Node *)c; } ; diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index d65df553ac..33e7cce420 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.109 2003/02/13 20:45:21 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.110 2003/02/16 02:30:38 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -922,17 +922,10 @@ buildMergedJoinVar(JoinType jointype, Var *l_colvar, Var *r_colvar) * Here we must build a COALESCE expression to ensure that * the join output is non-null if either input is. */ - CaseExpr *c = makeNode(CaseExpr); - CaseWhen *w = makeNode(CaseWhen); - NullTest *n = makeNode(NullTest); - - n->arg = (Expr *) l_node; - n->nulltesttype = IS_NOT_NULL; - w->expr = (Expr *) n; - w->result = (Expr *) l_node; - c->casetype = outcoltype; - c->args = makeList1(w); - c->defresult = (Expr *) r_node; + CoalesceExpr *c = makeNode(CoalesceExpr); + + c->coalescetype = outcoltype; + c->args = makeList2(l_node, r_node); res_node = (Node *) c; break; } diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index f059a1db2c..2ec65b52c2 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.145 2003/02/13 18:29:07 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.146 2003/02/16 02:30:38 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -277,6 +277,24 @@ transformExpr(ParseState *pstate, Node *expr) NodeSetTag(result, T_DistinctExpr); } break; + case AEXPR_NULLIF: + { + Node *lexpr = transformExpr(pstate, + a->lexpr); + Node *rexpr = transformExpr(pstate, + a->rexpr); + + result = (Node *) make_op(a->name, + lexpr, + rexpr); + if (((OpExpr *) result)->opresulttype != BOOLOID) + elog(ERROR, "NULLIF requires = operator to yield boolean"); + /* + * We rely on NullIfExpr and OpExpr being same struct + */ + NodeSetTag(result, T_NullIfExpr); + } + break; case AEXPR_OF: { /* @@ -615,6 +633,43 @@ transformExpr(ParseState *pstate, Node *expr) break; } + case T_CoalesceExpr: + { + CoalesceExpr *c = (CoalesceExpr *) expr; + CoalesceExpr *newc = makeNode(CoalesceExpr); + List *newargs = NIL; + List *newcoercedargs = NIL; + List *typeids = NIL; + List *args; + + foreach(args, c->args) + { + Node *e = (Node *) lfirst(args); + Node *newe; + + newe = transformExpr(pstate, e); + newargs = lappend(newargs, newe); + typeids = lappendo(typeids, exprType(newe)); + } + + newc->coalescetype = select_common_type(typeids, "COALESCE"); + + /* Convert arguments if necessary */ + foreach(args, newargs) + { + Node *e = (Node *) lfirst(args); + Node *newe; + + newe = coerce_to_common_type(e, newc->coalescetype, + "COALESCE"); + newcoercedargs = lappend(newcoercedargs, newe); + } + + newc->args = newcoercedargs; + result = (Node *) newc; + break; + } + case T_NullTest: { NullTest *n = (NullTest *) expr; @@ -680,6 +735,7 @@ transformExpr(ParseState *pstate, Node *expr) case T_FuncExpr: case T_OpExpr: case T_DistinctExpr: + case T_NullIfExpr: case T_BoolExpr: case T_FieldSelect: case T_RelabelType: @@ -1020,6 +1076,12 @@ exprType(Node *expr) case T_CaseWhen: type = exprType((Node *) ((CaseWhen *) expr)->result); break; + case T_CoalesceExpr: + type = ((CoalesceExpr *) expr)->coalescetype; + break; + case T_NullIfExpr: + type = exprType((Node *) lfirst(((NullIfExpr *) expr)->args)); + break; case T_NullTest: type = BOOLOID; break; @@ -1126,6 +1188,37 @@ exprTypmod(Node *expr) return typmod; } break; + case T_CoalesceExpr: + { + /* + * If all the alternatives agree on type/typmod, return + * that typmod, else use -1 + */ + CoalesceExpr *cexpr = (CoalesceExpr *) expr; + Oid coalescetype = cexpr->coalescetype; + int32 typmod; + List *arg; + + typmod = exprTypmod((Node *) lfirst(cexpr->args)); + foreach(arg, cexpr->args) + { + Node *e = (Node *) lfirst(arg); + + if (exprType(e) != coalescetype) + return -1; + if (exprTypmod(e) != typmod) + return -1; + } + return typmod; + } + break; + case T_NullIfExpr: + { + NullIfExpr *nexpr = (NullIfExpr *) expr; + + return exprTypmod((Node *) lfirst(nexpr->args)); + } + break; case T_CoerceToDomain: return ((CoerceToDomain *) expr)->resulttypmod; case T_CoerceToDomainValue: diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 70aaf18ef5..4108e7557d 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.97 2003/02/13 05:53:46 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.98 2003/02/16 02:30:38 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -482,6 +482,14 @@ FigureColnameInternal(Node *node, char **name) case T_FuncCall: *name = strVal(llast(((FuncCall *) node)->funcname)); return 2; + case T_A_Expr: + /* make nullif() act like a regular function */ + if (((A_Expr *) node)->kind == AEXPR_NULLIF) + { + *name = "nullif"; + return 2; + } + break; case T_A_Const: if (((A_Const *) node)->typename != NULL) { @@ -510,6 +518,10 @@ FigureColnameInternal(Node *node, char **name) return 1; } break; + case T_CoalesceExpr: + /* make coalesce() act like a regular function */ + *name = "coalesce"; + return 2; default: break; } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 205ffd7540..dfed27f89d 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.135 2003/02/13 05:10:39 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.136 2003/02/16 02:30:39 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -2238,6 +2238,46 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_CoalesceExpr: + { + CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; + List *arg; + char *sep; + + appendStringInfo(buf, "COALESCE("); + sep = ""; + foreach(arg, coalesceexpr->args) + { + Node *e = (Node *) lfirst(arg); + + appendStringInfo(buf, sep); + get_rule_expr(e, context, true); + sep = ", "; + } + appendStringInfo(buf, ")"); + } + break; + + case T_NullIfExpr: + { + NullIfExpr *nullifexpr = (NullIfExpr *) node; + List *arg; + char *sep; + + appendStringInfo(buf, "NULLIF("); + sep = ""; + foreach(arg, nullifexpr->args) + { + Node *e = (Node *) lfirst(arg); + + appendStringInfo(buf, sep); + get_rule_expr(e, context, true); + sep = ", "; + } + appendStringInfo(buf, ")"); + } + break; + case T_NullTest: { NullTest *ntest = (NullTest *) node; diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 230f1e277a..03e452121f 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: catversion.h,v 1.176 2003/02/13 05:24:02 momjian Exp $ + * $Id: catversion.h,v 1.177 2003/02/16 02:30:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200302131 +#define CATALOG_VERSION_NO 200302151 #endif diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 8d2b130598..591870be51 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: execnodes.h,v 1.94 2003/02/09 00:30:39 tgl Exp $ + * $Id: execnodes.h,v 1.95 2003/02/16 02:30:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -441,8 +441,9 @@ typedef struct ArrayRefExprState /* ---------------- * FuncExprState node * - * Although named for FuncExpr, this is also used for OpExpr and DistinctExpr - * nodes; be careful to check what xprstate.expr is actually pointing at! + * Although named for FuncExpr, this is also used for OpExpr, DistinctExpr, + * and NullIf nodes; be careful to check what xprstate.expr is actually + * pointing at! * ---------------- */ typedef struct FuncExprState @@ -539,6 +540,16 @@ typedef struct CaseWhenState ExprState *result; /* substitution result */ } CaseWhenState; +/* ---------------- + * CoalesceExprState node + * ---------------- + */ +typedef struct CoalesceExprState +{ + ExprState xprstate; + List *args; /* the arguments */ +} CoalesceExprState; + /* ---------------- * CoerceToDomainState node * ---------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index a218790194..356f4b60fa 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: nodes.h,v 1.136 2003/02/03 21:15:44 tgl Exp $ + * $Id: nodes.h,v 1.137 2003/02/16 02:30:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -112,6 +112,8 @@ typedef enum NodeTag T_RelabelType, T_CaseExpr, T_CaseWhen, + T_CoalesceExpr, + T_NullIfExpr, T_NullTest, T_BooleanTest, T_CoerceToDomain, @@ -136,6 +138,7 @@ typedef enum NodeTag T_SubPlanState, T_CaseExprState, T_CaseWhenState, + T_CoalesceExprState, T_CoerceToDomainState, T_DomainConstraintState, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 9474bc6490..381c11c389 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.230 2003/02/13 05:20:03 momjian Exp $ + * $Id: parsenodes.h,v 1.231 2003/02/16 02:30:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -174,6 +174,7 @@ typedef enum A_Expr_Kind AEXPR_OR, AEXPR_NOT, AEXPR_DISTINCT, /* IS DISTINCT FROM - name must be "=" */ + AEXPR_NULLIF, /* NULLIF - name must be "=" */ AEXPR_OF /* IS (not) OF - name must be "=" or "!=" */ } A_Expr_Kind; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index be917c4f26..b8a358d614 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: primnodes.h,v 1.79 2003/02/09 00:30:40 tgl Exp $ + * $Id: primnodes.h,v 1.80 2003/02/16 02:30:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -537,6 +537,24 @@ typedef struct CaseWhen Expr *result; /* substitution result */ } CaseWhen; +/* + * CoalesceExpr - a COALESCE expression + */ +typedef struct CoalesceExpr +{ + Expr xpr; + Oid coalescetype; /* type of expression result */ + List *args; /* the arguments */ +} CoalesceExpr; + +/* + * NullIfExpr - a NULLIF expression + * + * Like DistinctExpr, this is represented the same as an OpExpr referencing + * the "=" operator for x and y. + */ +typedef OpExpr NullIfExpr; + /* ---------------- * NullTest * diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 7d4785ef7d..3486f1ccd2 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.78 2003/02/03 21:15:45 tgl Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.79 2003/02/16 02:30:39 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -3628,6 +3628,28 @@ exec_simple_check_node(Node *node) return TRUE; } + case T_CoalesceExpr: + { + CoalesceExpr *expr = (CoalesceExpr *) node; + + if (!exec_simple_check_node((Node *) expr->args)) + return FALSE; + + return TRUE; + } + + case T_NullIfExpr: + { + NullIfExpr *expr = (NullIfExpr *) node; + + if (expr->opretset) + return FALSE; + if (!exec_simple_check_node((Node *) expr->args)) + return FALSE; + + return TRUE; + } + case T_NullTest: return exec_simple_check_node((Node *) ((NullTest *) node)->arg); diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out index 2b53bc699d..409a5a536a 100644 --- a/src/test/regress/expected/case.out +++ b/src/test/regress/expected/case.out @@ -154,32 +154,32 @@ SELECT * FROM CASE_TBL WHERE NULLIF(f,i) = 2; SELECT COALESCE(a.f, b.i, b.j) FROM CASE_TBL a, CASE2_TBL b; - case -------- - 10.1 - 10.1 - 10.1 - 10.1 - 10.1 - 10.1 - 20.2 - 20.2 - 20.2 - 20.2 - 20.2 - 20.2 - -30.3 - -30.3 - -30.3 - -30.3 - -30.3 - -30.3 - 1 - 2 - 3 - 2 - 1 - -6 + coalesce +---------- + 10.1 + 10.1 + 10.1 + 10.1 + 10.1 + 10.1 + 20.2 + 20.2 + 20.2 + 20.2 + 20.2 + 20.2 + -30.3 + -30.3 + -30.3 + -30.3 + -30.3 + -30.3 + 1 + 2 + 3 + 2 + 1 + -6 (24 rows) SELECT *