aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/func.sgml13
-rw-r--r--src/backend/catalog/dependency.c10
-rw-r--r--src/backend/executor/execQual.c140
-rw-r--r--src/backend/nodes/copyfuncs.c41
-rw-r--r--src/backend/nodes/equalfuncs.c39
-rw-r--r--src/backend/nodes/outfuncs.c33
-rw-r--r--src/backend/nodes/readfuncs.c47
-rw-r--r--src/backend/optimizer/path/costsize.c5
-rw-r--r--src/backend/optimizer/plan/setrefs.c6
-rw-r--r--src/backend/optimizer/util/clauses.c97
-rw-r--r--src/backend/parser/gram.y29
-rw-r--r--src/backend/parser/parse_clause.c17
-rw-r--r--src/backend/parser/parse_expr.c95
-rw-r--r--src/backend/parser/parse_target.c14
-rw-r--r--src/backend/utils/adt/ruleutils.c42
-rw-r--r--src/include/catalog/catversion.h4
-rw-r--r--src/include/nodes/execnodes.h17
-rw-r--r--src/include/nodes/nodes.h5
-rw-r--r--src/include/nodes/parsenodes.h3
-rw-r--r--src/include/nodes/primnodes.h20
-rw-r--r--src/pl/plpgsql/src/pl_exec.c24
-rw-r--r--src/test/regress/expected/case.out52
22 files changed, 645 insertions, 108 deletions
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f2d84a969aa..fd247be28c2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,5 +1,5 @@
<!--
-$Header: /cvsroot/pgsql/doc/src/sgml/func.sgml,v 1.139 2003/02/13 05:24:01 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/func.sgml,v 1.140 2003/02/16 02:30:36 tgl Exp $
PostgreSQL documentation
-->
@@ -6295,17 +6295,6 @@ SELECT NULLIF(value, '(none)') ...
</programlisting>
</para>
- <tip>
- <para>
- <function>COALESCE</function> and <function>NULLIF</function> are
- just shorthand for <token>CASE</token> expressions. They are actually
- converted into <token>CASE</token> expressions at a very early stage
- of processing, and subsequent processing thinks it is dealing with
- <token>CASE</token>. Thus an incorrect <function>COALESCE</function> or
- <function>NULLIF</function> usage may draw an error message that
- refers to <token>CASE</token>.
- </para>
- </tip>
</sect2>
</sect1>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index f406a574c92..35df2eae264 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 a2583fcc4c9..968617c39a9 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
*
@@ -1430,6 +1435,91 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
}
/* ----------------------------------------------------------------
+ * 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
*
* Evaluate a NullTest node.
@@ -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 aa7a7efcc8b..2698f084787 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)
@@ -920,6 +920,37 @@ _copyCaseWhen(CaseWhen *from)
}
/*
+ * _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
*/
static NullTest *
@@ -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 c0bd77756a4..378d8e44031 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 $
*
*-------------------------------------------------------------------------
*/
@@ -379,6 +379,37 @@ _equalCaseWhen(CaseWhen *a, CaseWhen *b)
}
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)
{
COMPARE_NODE_FIELD(arg);
@@ -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 134ee4328e7..8485244492c 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*
@@ -754,6 +754,27 @@ _outCaseWhen(StringInfo str, CaseWhen *node)
}
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)
{
WRITE_NODE_TYPE("NULLTEST");
@@ -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 f37856728b1..410d092c916 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
@@ -607,6 +607,47 @@ _readCaseWhen(void)
}
/*
+ * _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
*/
static NullTest *
@@ -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 54e47e24243..21bc152ce6e 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 123b96f1880..4e17a85eb4b 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 71b5909d207..40e440a3754 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 5a7dff919d8..c593196dfc8 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 d65df553acf..33e7cce4203 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 f059a1db2c0..2ec65b52c21 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 70aaf18ef58..4108e7557da 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 205ffd7540b..dfed27f89da 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 230f1e277ac..03e452121f0 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 8d2b1305984..591870be515 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
@@ -540,6 +541,16 @@ typedef struct CaseWhenState
} 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 a218790194e..356f4b60fa3 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 9474bc6490b..381c11c3893 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 be917c4f260..b8a358d6147 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 7d4785ef7d5..3486f1ccd23 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 2b53bc699dc..409a5a536aa 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 *