From 116d2bba7eeaf25c544bc187e3ad2a8677a9a22c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 19 Jun 2001 22:39:12 +0000 Subject: [PATCH] Add IS UNKNOWN, IS NOT UNKNOWN boolean tests, fix the existing boolean tests to return the correct results per SQL9x when given NULL inputs. Reimplement these tests as well as IS [NOT] NULL to have their own expression node types, instead of depending on special functions. From Joe Conway, with a little help from Tom Lane. --- doc/src/sgml/func.sgml | 17 +++- doc/src/sgml/syntax.sgml | 4 +- src/backend/executor/execQual.c | 138 +++++++++++++++++++++++++- src/backend/nodes/copyfuncs.c | 44 ++++++++- src/backend/nodes/equalfuncs.c | 28 +++++- src/backend/nodes/outfuncs.c | 41 ++++++-- src/backend/nodes/readfuncs.c | 56 ++++++++++- src/backend/optimizer/util/clauses.c | 26 ++++- src/backend/parser/gram.y | 143 ++++++++++++++++++--------- src/backend/parser/keywords.c | 3 +- src/backend/parser/parse_clause.c | 32 +++--- src/backend/parser/parse_coerce.c | 26 ++++- src/backend/parser/parse_expr.c | 120 ++++++++++++++-------- src/backend/utils/adt/ruleutils.c | 76 ++++++++++---- src/include/catalog/catversion.h | 4 +- src/include/nodes/nodes.h | 6 +- src/include/nodes/parsenodes.h | 51 +++++++++- src/include/parser/parse_coerce.h | 4 +- 18 files changed, 666 insertions(+), 153 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index dc646320cb..00d476edb6 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,4 +1,4 @@ - + Functions and Operators @@ -273,6 +273,21 @@ Microsoft Access) to work, but this may be discontinued in a future release. + + + Boolean values can be tested using the constructs + +expression IS TRUE +expression IS NOT TRUE +expression IS FALSE +expression IS NOT FALSE +expression IS UNKNOWN +expression IS NOT UNKNOWN + + These are similar to IS NULL in that they will + always return TRUE or FALSE, never NULL, even when the operand is NULL. + A NULL input is treated as the logical value UNKNOWN. + diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 9234e3c26d..300851235c 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -1,5 +1,5 @@ @@ -1060,7 +1060,7 @@ SELECT (5 !) - 6; IS - test for TRUE, FALSE, NULL + test for TRUE, FALSE, UNKNOWN, NULL diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 84aa271629..fb950fdfd1 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.86 2001/04/19 04:29:02 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.87 2001/06/19 22:39:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -62,6 +62,10 @@ static Datum ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull); static Datum ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull); static Datum ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalNullTest(NullTest *ntest, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalBooleanTest(BooleanTest *btest, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); /*---------- @@ -1091,6 +1095,126 @@ ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext, return (Datum) 0; } +/* ---------------------------------------------------------------- + * ExecEvalNullTest + * + * Evaluate a NullTest node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalNullTest(NullTest *ntest, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + Datum result; + + result = ExecEvalExpr(ntest->arg, econtext, isNull, isDone); + switch (ntest->nulltesttype) + { + case IS_NULL: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(true); + } + else + return BoolGetDatum(false); + case IS_NOT_NULL: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(false); + } + else + return BoolGetDatum(true); + default: + elog(ERROR, "ExecEvalNullTest: unexpected nulltesttype %d", + (int) ntest->nulltesttype); + return (Datum) 0; /* keep compiler quiet */ + } +} + +/* ---------------------------------------------------------------- + * ExecEvalBooleanTest + * + * Evaluate a BooleanTest node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalBooleanTest(BooleanTest *btest, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + Datum result; + + result = ExecEvalExpr(btest->arg, econtext, isNull, isDone); + switch (btest->booltesttype) + { + case IS_TRUE: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(false); + } + else if (DatumGetBool(result)) + return BoolGetDatum(true); + else + return BoolGetDatum(false); + case IS_NOT_TRUE: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(true); + } + else if (DatumGetBool(result)) + return BoolGetDatum(false); + else + return BoolGetDatum(true); + case IS_FALSE: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(false); + } + else if (DatumGetBool(result)) + return BoolGetDatum(false); + else + return BoolGetDatum(true); + case IS_NOT_FALSE: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(true); + } + else if (DatumGetBool(result)) + return BoolGetDatum(true); + else + return BoolGetDatum(false); + case IS_UNKNOWN: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(true); + } + else + return BoolGetDatum(false); + case IS_NOT_UNKNOWN: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(false); + } + else + return BoolGetDatum(true); + default: + elog(ERROR, "ExecEvalBooleanTest: unexpected booltesttype %d", + (int) btest->booltesttype); + return (Datum) 0; /* keep compiler quiet */ + } +} + /* ---------------------------------------------------------------- * ExecEvalFieldSelect * @@ -1266,6 +1390,18 @@ ExecEvalExpr(Node *expression, isNull, isDone); break; + case T_NullTest: + retDatum = ExecEvalNullTest((NullTest *) expression, + econtext, + isNull, + isDone); + break; + case T_BooleanTest: + retDatum = ExecEvalBooleanTest((BooleanTest *) expression, + econtext, + isNull, + isDone); + break; default: elog(ERROR, "ExecEvalExpr: unknown expression type %d", diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 77ae4fb781..6cf5b35d26 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.144 2001/06/09 23:21:54 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.145 2001/06/19 22:39:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1017,6 +1017,42 @@ _copyCaseWhen(CaseWhen *from) return newnode; } +/* ---------------- + * _copyNullTest + * ---------------- + */ +static NullTest * +_copyNullTest(NullTest *from) +{ + NullTest *newnode = makeNode(NullTest); + + /* + * copy remainder of node + */ + Node_Copy(from, newnode, arg); + newnode->nulltesttype = from->nulltesttype; + + return newnode; +} + +/* ---------------- + * _copyBooleanTest + * ---------------- + */ +static BooleanTest * +_copyBooleanTest(BooleanTest *from) +{ + BooleanTest *newnode = makeNode(BooleanTest); + + /* + * copy remainder of node + */ + Node_Copy(from, newnode, arg); + newnode->booltesttype = from->booltesttype; + + return newnode; +} + static ArrayRef * _copyArrayRef(ArrayRef *from) { @@ -2954,6 +2990,12 @@ copyObject(void *from) case T_CaseWhen: retval = _copyCaseWhen(from); break; + case T_NullTest: + retval = _copyNullTest(from); + break; + case T_BooleanTest: + retval = _copyBooleanTest(from); + break; case T_FkConstraint: retval = _copyFkConstraint(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index f7bfcc1977..b12b4c2912 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -20,7 +20,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.92 2001/06/09 23:21:54 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.93 2001/06/19 22:39:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1712,6 +1712,26 @@ _equalCaseWhen(CaseWhen *a, CaseWhen *b) return true; } +static bool +_equalNullTest(NullTest *a, NullTest *b) +{ + if (!equal(a->arg, b->arg)) + return false; + if (a->nulltesttype != b->nulltesttype) + return false; + return true; +} + +static bool +_equalBooleanTest(BooleanTest *a, BooleanTest *b) +{ + if (!equal(a->arg, b->arg)) + return false; + if (a->booltesttype != b->booltesttype) + return false; + return true; +} + /* * Stuff from pg_list.h */ @@ -2120,6 +2140,12 @@ equal(void *a, void *b) case T_CaseWhen: retval = _equalCaseWhen(a, b); break; + case T_NullTest: + retval = _equalNullTest(a, b); + break; + case T_BooleanTest: + retval = _equalBooleanTest(a, b); + break; case T_FkConstraint: retval = _equalFkConstraint(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index ebcacd4975..e555e9591e 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -5,7 +5,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.141 2001/05/20 20:28:18 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.142 2001/06/19 22:39:11 tgl Exp $ * * NOTES * Every (plan) node in POSTGRES has an associated "out" routine which @@ -1259,12 +1259,6 @@ _outAExpr(StringInfo str, A_Expr *node) case NOT: appendStringInfo(str, "NOT "); break; - case ISNULL: - appendStringInfo(str, "ISNULL "); - break; - case NOTNULL: - appendStringInfo(str, "NOTNULL "); - break; case OP: _outToken(str, node->opname); appendStringInfo(str, " "); @@ -1402,6 +1396,32 @@ _outCaseWhen(StringInfo str, CaseWhen *node) _outNode(str, node->result); } +/* + * NullTest + */ +static void +_outNullTest(StringInfo str, NullTest *node) +{ + appendStringInfo(str, " NULLTEST :arg "); + _outNode(str, node->arg); + + appendStringInfo(str, " :nulltesttype %d ", + (int) node->nulltesttype); +} + +/* + * BooleanTest + */ +static void +_outBooleanTest(StringInfo str, BooleanTest *node) +{ + appendStringInfo(str, " BOOLEANTEST :arg "); + _outNode(str, node->arg); + + appendStringInfo(str, " :booltesttype %d ", + (int) node->booltesttype); +} + /* * _outNode - * converts a Node into ascii string and append it to 'str' @@ -1639,7 +1659,12 @@ _outNode(StringInfo str, void *obj) case T_CaseWhen: _outCaseWhen(str, obj); break; - + case T_NullTest: + _outNullTest(str, obj); + break; + case T_BooleanTest: + _outBooleanTest(str, obj); + break; case T_VariableSetStmt: break; case T_SelectStmt: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index a83f0b64db..2f0dec048b 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.110 2001/06/05 05:26:04 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.111 2001/06/19 22:39:11 tgl Exp $ * * NOTES * Most of the read functions for plan nodes are tested. (In fact, they @@ -859,6 +859,56 @@ _readCaseWhen(void) return local_node; } +/* ---------------- + * _readNullTest + * + * NullTest is a subclass of Node + * ---------------- + */ +static NullTest * +_readNullTest(void) +{ + NullTest *local_node; + char *token; + int length; + + local_node = makeNode(NullTest); + + token = pg_strtok(&length); /* eat :arg */ + local_node->arg = nodeRead(true); /* now read it */ + + token = pg_strtok(&length); /* eat :nulltesttype */ + token = pg_strtok(&length); /* get nulltesttype */ + local_node->nulltesttype = (NullTestType) atoi(token); + + return local_node; +} + +/* ---------------- + * _readBooleanTest + * + * BooleanTest is a subclass of Node + * ---------------- + */ +static BooleanTest * +_readBooleanTest(void) +{ + BooleanTest *local_node; + char *token; + int length; + + local_node = makeNode(BooleanTest); + + token = pg_strtok(&length); /* eat :arg */ + local_node->arg = nodeRead(true); /* now read it */ + + token = pg_strtok(&length); /* eat :booltesttype */ + token = pg_strtok(&length); /* get booltesttype */ + local_node->booltesttype = (BoolTestType) atoi(token); + + return local_node; +} + /* ---------------- * _readVar * @@ -1966,6 +2016,10 @@ parsePlanString(void) return_value = _readCaseExpr(); else if (length == 4 && strncmp(token, "WHEN", length) == 0) return_value = _readCaseWhen(); + else if (length == 8 && strncmp(token, "NULLTEST", length) == 0) + return_value = _readNullTest(); + else if (length == 11 && strncmp(token, "BOOLEANTEST", length) == 0) + return_value = _readBooleanTest(); else elog(ERROR, "badly formatted planstring \"%.10s\"...", token); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index e0cc97e3a1..6ee962fd75 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.85 2001/05/20 20:28:19 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.86 2001/06/19 22:39:11 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -1580,6 +1580,10 @@ expression_tree_walker(Node *node, return true; } break; + case T_NullTest: + return walker(((NullTest *) node)->arg, context); + case T_BooleanTest: + return walker(((BooleanTest *) node)->arg, context); case T_SubLink: { SubLink *sublink = (SubLink *) node; @@ -1933,6 +1937,26 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_NullTest: + { + NullTest *ntest = (NullTest *) node; + NullTest *newnode; + + FLATCOPY(newnode, ntest, NullTest); + MUTATE(newnode->arg, ntest->arg, Node *); + return (Node *) newnode; + } + break; + case T_BooleanTest: + { + BooleanTest *btest = (BooleanTest *) node; + BooleanTest *newnode; + + FLATCOPY(newnode, btest, BooleanTest); + MUTATE(newnode->arg, btest->arg, Node *); + return (Node *) newnode; + } + break; case T_SubLink: { diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 263830244d..e47c3f0b33 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.230 2001/06/09 23:21:54 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.231 2001/06/19 22:39:11 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -315,7 +315,7 @@ static void doNegateFloat(Value *v); SCHEMA, SCROLL, SECOND_P, SELECT, SESSION, SESSION_USER, SET, SOME, SUBSTRING, TABLE, TEMPORARY, THEN, TIME, TIMESTAMP, TIMEZONE_HOUR, TIMEZONE_MINUTE, TO, TRAILING, TRANSACTION, TRIM, TRUE_P, - UNION, UNIQUE, UPDATE, USER, USING, + UNION, UNIQUE, UNKNOWN, UPDATE, USER, USING, VALUES, VARCHAR, VARYING, VIEW, WHEN, WHERE, WITH, WORK, YEAR_P, ZONE @@ -386,7 +386,7 @@ static void doNegateFloat(Value *v); %left Op /* multi-character ops and user-defined operators */ %nonassoc NOTNULL %nonassoc ISNULL -%nonassoc IS NULL_P TRUE_P FALSE_P /* sets precedence for IS NULL, etc */ +%nonassoc IS NULL_P TRUE_P FALSE_P UNKNOWN /* sets precedence for IS NULL, etc */ %left '+' '-' %left '*' '/' '%' %left '^' @@ -4434,9 +4434,19 @@ a_expr: c_expr * (like Microsoft's). Turn these into IS NULL exprs. */ if (exprIsNullConstant($3)) - $$ = makeA_Expr(ISNULL, NULL, $1, NULL); + { + NullTest *n = makeNode(NullTest); + n->arg = $1; + n->nulltesttype = IS_NULL; + $$ = (Node *)n; + } else if (exprIsNullConstant($1)) - $$ = makeA_Expr(ISNULL, NULL, $3, NULL); + { + NullTest *n = makeNode(NullTest); + n->arg = $3; + n->nulltesttype = IS_NULL; + $$ = (Node *)n; + } else $$ = makeA_Expr(OP, "=", $1, $3); } @@ -4499,59 +4509,95 @@ a_expr: c_expr n->agg_distinct = FALSE; $$ = makeA_Expr(OP, "!~~*", $1, (Node *) n); } - + /* NullTest clause + * Define SQL92-style Null test clause. + * Allow two forms described in the standard: + * a IS NULL + * a IS NOT NULL + * Allow two SQL extensions + * a ISNULL + * a NOTNULL + * NOTE: this is not yet fully SQL-compatible, since SQL92 + * allows a row constructor as argument, not just a scalar. + */ | a_expr ISNULL - { $$ = makeA_Expr(ISNULL, NULL, $1, NULL); } + { + NullTest *n = makeNode(NullTest); + n->arg = $1; + n->nulltesttype = IS_NULL; + $$ = (Node *)n; + } | a_expr IS NULL_P - { $$ = makeA_Expr(ISNULL, NULL, $1, NULL); } + { + NullTest *n = makeNode(NullTest); + n->arg = $1; + n->nulltesttype = IS_NULL; + $$ = (Node *)n; + } | a_expr NOTNULL - { $$ = makeA_Expr(NOTNULL, NULL, $1, NULL); } + { + NullTest *n = makeNode(NullTest); + n->arg = $1; + n->nulltesttype = IS_NOT_NULL; + $$ = (Node *)n; + } | a_expr IS NOT NULL_P - { $$ = makeA_Expr(NOTNULL, NULL, $1, NULL); } + { + NullTest *n = makeNode(NullTest); + n->arg = $1; + n->nulltesttype = IS_NOT_NULL; + $$ = (Node *)n; + } /* IS TRUE, IS FALSE, etc used to be function calls * but let's make them expressions to allow the optimizer * a chance to eliminate them if a_expr is a constant string. * - thomas 1997-12-22 + * + * Created BooleanTest Node type, and changed handling + * for NULL inputs + * - jec 2001-06-18 */ | a_expr IS TRUE_P { - A_Const *n = makeNode(A_Const); - n->val.type = T_String; - n->val.val.str = "t"; - n->typename = makeNode(TypeName); - n->typename->name = xlateSqlType("bool"); - n->typename->typmod = -1; - $$ = makeA_Expr(OP, "=", $1,(Node *)n); - } - | a_expr IS NOT FALSE_P - { - A_Const *n = makeNode(A_Const); - n->val.type = T_String; - n->val.val.str = "t"; - n->typename = makeNode(TypeName); - n->typename->name = xlateSqlType("bool"); - n->typename->typmod = -1; - $$ = makeA_Expr(OP, "=", $1,(Node *)n); - } - | a_expr IS FALSE_P - { - A_Const *n = makeNode(A_Const); - n->val.type = T_String; - n->val.val.str = "f"; - n->typename = makeNode(TypeName); - n->typename->name = xlateSqlType("bool"); - n->typename->typmod = -1; - $$ = makeA_Expr(OP, "=", $1,(Node *)n); + BooleanTest *b = makeNode(BooleanTest); + b->arg = $1; + b->booltesttype = IS_TRUE; + $$ = (Node *)b; } | a_expr IS NOT TRUE_P { - A_Const *n = makeNode(A_Const); - n->val.type = T_String; - n->val.val.str = "f"; - n->typename = makeNode(TypeName); - n->typename->name = xlateSqlType("bool"); - n->typename->typmod = -1; - $$ = makeA_Expr(OP, "=", $1,(Node *)n); + BooleanTest *b = makeNode(BooleanTest); + b->arg = $1; + b->booltesttype = IS_NOT_TRUE; + $$ = (Node *)b; + } + | a_expr IS FALSE_P + { + BooleanTest *b = makeNode(BooleanTest); + b->arg = $1; + b->booltesttype = IS_FALSE; + $$ = (Node *)b; + } + | a_expr IS NOT FALSE_P + { + BooleanTest *b = makeNode(BooleanTest); + b->arg = $1; + b->booltesttype = IS_NOT_FALSE; + $$ = (Node *)b; + } + | a_expr IS UNKNOWN + { + BooleanTest *b = makeNode(BooleanTest); + b->arg = $1; + b->booltesttype = IS_UNKNOWN; + $$ = (Node *)b; + } + | a_expr IS NOT UNKNOWN + { + BooleanTest *b = makeNode(BooleanTest); + b->arg = $1; + b->booltesttype = IS_NOT_UNKNOWN; + $$ = (Node *)b; } | a_expr BETWEEN b_expr AND b_expr %prec BETWEEN { @@ -5206,12 +5252,14 @@ case_expr: CASE case_arg when_clause_list case_default END_TRANS | COALESCE '(' expr_list ')' { CaseExpr *c = makeNode(CaseExpr); - CaseWhen *w; List *l; foreach (l,$3) { - w = makeNode(CaseWhen); - w->expr = makeA_Expr(NOTNULL, NULL, lfirst(l), NULL); + CaseWhen *w = makeNode(CaseWhen); + NullTest *n = makeNode(NullTest); + n->arg = lfirst(l); + n->nulltesttype = IS_NOT_NULL; + w->expr = (Node *) n; w->result = lfirst(l); c->args = lappend(c->args, w); } @@ -5765,6 +5813,7 @@ ColLabel: ColId { $$ = $1; } | TRUE_P { $$ = "true"; } | UNION { $$ = "union"; } | UNIQUE { $$ = "unique"; } + | UNKNOWN { $$ = "unknown"; } | USER { $$ = "user"; } | USING { $$ = "using"; } | VACUUM { $$ = "vacuum"; } diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index 6064ca8a8f..ccdfb88a2e 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.92 2001/05/08 21:06:43 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.93 2001/06/19 22:39:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -265,6 +265,7 @@ static ScanKeyword ScanKeywords[] = { {"type", TYPE_P}, {"union", UNION}, {"unique", UNIQUE}, + {"unknown", UNKNOWN}, {"unlisten", UNLISTEN}, {"until", UNTIL}, {"update", UPDATE}, diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 02c6a4ac8c..585b21b0f4 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.80 2001/05/18 21:24:19 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.81 2001/06/19 22:39:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -286,16 +286,14 @@ transformJoinUsingClause(ParseState *pstate, List *leftVars, List *rightVars) */ result = transformExpr(pstate, result, EXPR_COLUMN_FIRST); + /* + * We expect the result to yield bool directly, otherwise complain. + * We could try coerce_to_boolean() here, but it seems likely that an + * "=" operator that doesn't return bool is wrong anyway. + */ if (exprType(result) != BOOLOID) - { - - /* - * This could only happen if someone defines a funny version of - * '=' - */ elog(ERROR, "JOIN/USING clause must return type bool, not type %s", typeidTypeName(exprType(result))); - } return result; } /* transformJoinUsingClause() */ @@ -328,11 +326,10 @@ transformJoinOnClause(ParseState *pstate, JoinExpr *j, /* This part is just like transformWhereClause() */ result = transformExpr(pstate, j->quals, EXPR_COLUMN_FIRST); - if (exprType(result) != BOOLOID) - { + + if (! coerce_to_boolean(pstate, &result)) elog(ERROR, "JOIN/ON clause must return type bool, not type %s", typeidTypeName(exprType(result))); - } pstate->p_namespace = save_namespace; @@ -689,11 +686,11 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels) /* Need COALESCE(l_colvar, r_colvar) */ CaseExpr *c = makeNode(CaseExpr); CaseWhen *w = makeNode(CaseWhen); - A_Expr *a = makeNode(A_Expr); + NullTest *n = makeNode(NullTest); - a->oper = NOTNULL; - a->lexpr = l_colvar; - w->expr = (Node *) a; + n->arg = l_colvar; + n->nulltesttype = IS_NOT_NULL; + w->expr = (Node *) n; w->result = l_colvar; c->args = makeList1(w); c->defresult = r_colvar; @@ -777,11 +774,10 @@ transformWhereClause(ParseState *pstate, Node *clause) qual = transformExpr(pstate, clause, EXPR_COLUMN_FIRST); - if (exprType(qual) != BOOLOID) - { + if (! coerce_to_boolean(pstate, &qual)) elog(ERROR, "WHERE clause must return type bool, not type %s", typeidTypeName(exprType(qual))); - } + return qual; } diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 38f044217e..283fd30240 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.57 2001/05/22 16:37:16 petere Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.58 2001/06/19 22:39:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -321,6 +321,30 @@ coerce_type_typmod(ParseState *pstate, Node *node, } +/* coerce_to_boolean() + * Coerce an argument of a construct that requires boolean input + * (AND, OR, NOT, etc). + * + * If successful, update *pnode to be the transformed argument (if any + * transformation is needed), and return TRUE. If fail, return FALSE. + * (The caller must check for FALSE and emit a suitable error message.) + */ +bool +coerce_to_boolean(ParseState *pstate, Node **pnode) +{ + Oid inputTypeId = exprType(*pnode); + Oid targetTypeId; + + if (inputTypeId == BOOLOID) + return true; /* no work */ + targetTypeId = BOOLOID; + if (! can_coerce_type(1, &inputTypeId, &targetTypeId)) + return false; /* fail, but let caller choose error msg */ + *pnode = coerce_type(pstate, *pnode, inputTypeId, targetTypeId, -1); + return true; +} + + /* select_common_type() * Determine the common supertype of a list of input expression types. * This is used for determining the output type of CASE and UNION diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index a196779f44..5fda57f5f9 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.97 2001/06/04 23:27:23 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.98 2001/06/19 22:39:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -167,32 +167,6 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) result = (Node *) make_op(a->opname, lexpr, rexpr); } break; - case ISNULL: - { - Node *lexpr = transformExpr(pstate, - a->lexpr, - precedence); - - result = ParseFuncOrColumn(pstate, - "nullvalue", - makeList1(lexpr), - false, false, - precedence); - } - break; - case NOTNULL: - { - Node *lexpr = transformExpr(pstate, - a->lexpr, - precedence); - - result = ParseFuncOrColumn(pstate, - "nonnullvalue", - makeList1(lexpr), - false, false, - precedence); - } - break; case AND: { Node *lexpr = transformExpr(pstate, @@ -203,13 +177,15 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) precedence); Expr *expr = makeNode(Expr); - if (exprType(lexpr) != BOOLOID) + if (! coerce_to_boolean(pstate, &lexpr)) elog(ERROR, "left-hand side of AND is type '%s', not '%s'", - typeidTypeName(exprType(lexpr)), typeidTypeName(BOOLOID)); + typeidTypeName(exprType(lexpr)), + typeidTypeName(BOOLOID)); - if (exprType(rexpr) != BOOLOID) + if (! coerce_to_boolean(pstate, &rexpr)) elog(ERROR, "right-hand side of AND is type '%s', not '%s'", - typeidTypeName(exprType(rexpr)), typeidTypeName(BOOLOID)); + typeidTypeName(exprType(rexpr)), + typeidTypeName(BOOLOID)); expr->typeOid = BOOLOID; expr->opType = AND_EXPR; @@ -227,12 +203,16 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) precedence); Expr *expr = makeNode(Expr); - if (exprType(lexpr) != BOOLOID) + if (! coerce_to_boolean(pstate, &lexpr)) elog(ERROR, "left-hand side of OR is type '%s', not '%s'", - typeidTypeName(exprType(lexpr)), typeidTypeName(BOOLOID)); - if (exprType(rexpr) != BOOLOID) + typeidTypeName(exprType(lexpr)), + typeidTypeName(BOOLOID)); + + if (! coerce_to_boolean(pstate, &rexpr)) elog(ERROR, "right-hand side of OR is type '%s', not '%s'", - typeidTypeName(exprType(rexpr)), typeidTypeName(BOOLOID)); + typeidTypeName(exprType(rexpr)), + typeidTypeName(BOOLOID)); + expr->typeOid = BOOLOID; expr->opType = OR_EXPR; expr->args = makeList2(lexpr, rexpr); @@ -246,9 +226,11 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) precedence); Expr *expr = makeNode(Expr); - if (exprType(rexpr) != BOOLOID) + if (! coerce_to_boolean(pstate, &rexpr)) elog(ERROR, "argument to NOT is type '%s', not '%s'", - typeidTypeName(exprType(rexpr)), typeidTypeName(BOOLOID)); + typeidTypeName(exprType(rexpr)), + typeidTypeName(BOOLOID)); + expr->typeOid = BOOLOID; expr->opType = NOT_EXPR; expr->args = makeList1(rexpr); @@ -491,7 +473,8 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) CaseWhen *w = (CaseWhen *) expr; w->expr = transformExpr(pstate, w->expr, precedence); - if (exprType(w->expr) != BOOLOID) + + if (! coerce_to_boolean(pstate, &w->expr)) elog(ERROR, "WHEN clause must have a boolean result"); /* @@ -510,6 +493,59 @@ transformExpr(ParseState *pstate, Node *expr, int precedence) break; } + case T_NullTest: + { + NullTest *n = (NullTest *) expr; + + n->arg = transformExpr(pstate, n->arg, precedence); + /* the argument can be any type, so don't coerce it */ + result = expr; + break; + } + + case T_BooleanTest: + { + BooleanTest *b = (BooleanTest *) expr; + + b->arg = transformExpr(pstate, b->arg, precedence); + + if (! coerce_to_boolean(pstate, &b->arg)) + { + const char *clausename; + + switch (b->booltesttype) + { + case IS_TRUE: + clausename = "IS TRUE"; + break; + case IS_NOT_TRUE: + clausename = "IS NOT TRUE"; + break; + case IS_FALSE: + clausename = "IS FALSE"; + break; + case IS_NOT_FALSE: + clausename = "IS NOT FALSE"; + break; + case IS_UNKNOWN: + clausename = "IS UNKNOWN"; + break; + case IS_NOT_UNKNOWN: + clausename = "IS NOT UNKNOWN"; + break; + default: + elog(ERROR, "transformExpr: unexpected booltesttype %d", + (int) b->booltesttype); + clausename = NULL; /* keep compiler quiet */ + } + + elog(ERROR, "Argument of %s must be boolean", + clausename); + } + result = expr; + break; + } + /* * Quietly accept node types that may be presented when we are * called on an already-transformed tree. @@ -669,8 +705,14 @@ exprType(Node *expr) case T_CaseWhen: type = exprType(((CaseWhen *) expr)->result); break; + case T_NullTest: + type = BOOLOID; + break; + case T_BooleanTest: + type = BOOLOID; + break; case T_Ident: - /* is this right? */ + /* XXX is this right? */ type = UNKNOWNOID; break; default: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 5635b90a9f..ae1a8c1fb1 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.77 2001/04/18 17:04:24 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.78 2001/06/19 22:39:12 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -52,7 +52,6 @@ #include "parser/parse_expr.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" -#include "utils/fmgroids.h" #include "utils/lsyscache.h" @@ -1948,6 +1947,60 @@ get_rule_expr(Node *node, deparse_context *context) } break; + case T_NullTest: + { + NullTest *ntest = (NullTest *) node; + + appendStringInfo(buf, "(("); + get_rule_expr(ntest->arg, context); + switch (ntest->nulltesttype) + { + case IS_NULL: + appendStringInfo(buf, ") IS NULL)"); + break; + case IS_NOT_NULL: + appendStringInfo(buf, ") IS NOT NULL)"); + break; + default: + elog(ERROR, "get_rule_expr: unexpected nulltesttype %d", + (int) ntest->nulltesttype); + } + } + break; + + case T_BooleanTest: + { + BooleanTest *btest = (BooleanTest *) node; + + appendStringInfo(buf, "(("); + get_rule_expr(btest->arg, context); + switch (btest->booltesttype) + { + case IS_TRUE: + appendStringInfo(buf, ") IS TRUE)"); + break; + case IS_NOT_TRUE: + appendStringInfo(buf, ") IS NOT TRUE)"); + break; + case IS_FALSE: + appendStringInfo(buf, ") IS FALSE)"); + break; + case IS_NOT_FALSE: + appendStringInfo(buf, ") IS NOT FALSE)"); + break; + case IS_UNKNOWN: + appendStringInfo(buf, ") IS UNKNOWN)"); + break; + case IS_NOT_UNKNOWN: + appendStringInfo(buf, ") IS NOT UNKNOWN)"); + break; + default: + elog(ERROR, "get_rule_expr: unexpected booltesttype %d", + (int) btest->booltesttype); + } + } + break; + case T_SubLink: get_sublink_expr(node, context); break; @@ -1978,25 +2031,6 @@ get_func_expr(Expr *expr, deparse_context *context) List *l; char *sep; - /* - * nullvalue() and nonnullvalue() should get turned into special - * syntax - */ - if (funcoid == F_NULLVALUE) - { - appendStringInfoChar(buf, '('); - get_rule_expr((Node *) lfirst(expr->args), context); - appendStringInfo(buf, " ISNULL)"); - return; - } - if (funcoid == F_NONNULLVALUE) - { - appendStringInfoChar(buf, '('); - get_rule_expr((Node *) lfirst(expr->args), context); - appendStringInfo(buf, " NOTNULL)"); - return; - } - /* * Get the functions pg_proc tuple */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 91feaec19e..c34655308e 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: catversion.h,v 1.83 2001/06/16 18:59:31 tgl Exp $ + * $Id: catversion.h,v 1.84 2001/06/19 22:39:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200106161 +#define CATALOG_VERSION_NO 200106191 #endif diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index d62583c4d2..fe2d035784 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: nodes.h,v 1.90 2001/06/09 23:21:55 petere Exp $ + * $Id: nodes.h,v 1.91 2001/06/19 22:39:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -218,8 +218,8 @@ typedef enum NodeTag T_RangeTblEntry, T_SortClause, T_GroupClause, - T_SubSelectXXX, /* not used anymore; tag# available */ - T_oldJoinExprXXX, /* not used anymore; tag# available */ + T_NullTest, + T_BooleanTest, T_CaseExpr, T_CaseWhen, T_RowMarkXXX, /* not used anymore; tag# available */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index fe2d1bb7ff..43e64b6ad5 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.131 2001/06/09 23:21:55 petere Exp $ + * $Id: parsenodes.h,v 1.132 2001/06/19 22:39:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -983,9 +983,8 @@ typedef struct ParamNo typedef struct A_Expr { NodeTag type; - int oper; /* type of operation - * {OP,OR,AND,NOT,ISNULL,NOTNULL} */ - char *opname; /* name of operator/function */ + int oper; /* type of operation (OP,OR,AND,NOT) */ + char *opname; /* name of operator */ Node *lexpr; /* left argument */ Node *rexpr; /* right argument */ } A_Expr; @@ -1054,6 +1053,50 @@ typedef struct CaseWhen Node *result; /* substitution result */ } CaseWhen; +/* ---------------- + * NullTest + * + * NullTest represents the operation of testing a value for NULLness. + * Currently, we only support scalar input values, but eventually a + * row-constructor input should be supported. + * The appropriate test is performed and returned as a boolean Datum. + * ---------------- + */ + +typedef enum NullTestType +{ + IS_NULL, IS_NOT_NULL +} NullTestType; + +typedef struct NullTest +{ + NodeTag type; + Node *arg; /* input expression */ + NullTestType nulltesttype; /* IS NULL, IS NOT NULL */ +} NullTest; + +/* ---------------- + * BooleanTest + * + * BooleanTest represents the operation of determining whether a boolean + * is TRUE, FALSE, or UNKNOWN (ie, NULL). All six meaningful combinations + * are supported. Note that a NULL input does *not* cause a NULL result. + * The appropriate test is performed and returned as a boolean Datum. + * ---------------- + */ + +typedef enum BoolTestType +{ + IS_TRUE, IS_NOT_TRUE, IS_FALSE, IS_NOT_FALSE, IS_UNKNOWN, IS_NOT_UNKNOWN +} BoolTestType; + +typedef struct BooleanTest +{ + NodeTag type; + Node *arg; /* input expression */ + BoolTestType booltesttype; /* test type */ +} BooleanTest; + /* * ColumnDef - column definition (used in various creates) * diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index f81a3be830..1f508b1eae 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parse_coerce.h,v 1.28 2001/05/22 16:37:17 petere Exp $ + * $Id: parse_coerce.h,v 1.29 2001/06/19 22:39:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -136,6 +136,8 @@ extern Node *coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, extern Node *coerce_type_typmod(ParseState *pstate, Node *node, Oid targetTypeId, int32 atttypmod); +extern bool coerce_to_boolean(ParseState *pstate, Node **pnode); + extern Oid select_common_type(List *typeids, const char *context); extern Node *coerce_to_common_type(ParseState *pstate, Node *node, Oid targetTypeId,