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.
-<!-- $Header: /cvsroot/pgsql/doc/src/sgml/func.sgml,v 1.61 2001/06/15 21:03:07 tgl Exp $ -->
+<!-- $Header: /cvsroot/pgsql/doc/src/sgml/func.sgml,v 1.62 2001/06/19 22:39:08 tgl Exp $ -->
<chapter id="functions">
<title>Functions and Operators</title>
<productname>Microsoft Access</productname>) to work, but this may
be discontinued in a future release.
</para>
+
+ <para>
+ Boolean values can be tested using the constructs
+<synopsis>
+<replaceable>expression</replaceable> IS TRUE
+<replaceable>expression</replaceable> IS NOT TRUE
+<replaceable>expression</replaceable> IS FALSE
+<replaceable>expression</replaceable> IS NOT FALSE
+<replaceable>expression</replaceable> IS UNKNOWN
+<replaceable>expression</replaceable> IS NOT UNKNOWN
+</synopsis>
+ These are similar to <literal>IS NULL</literal> 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.
+ </para>
</sect1>
<!--
-$Header: /cvsroot/pgsql/doc/src/sgml/syntax.sgml,v 1.42 2001/05/12 22:51:35 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/syntax.sgml,v 1.43 2001/06/19 22:39:08 tgl Exp $
-->
<chapter id="sql-syntax">
<row>
<entry><token>IS</token></entry>
<entry></entry>
- <entry>test for TRUE, FALSE, NULL</entry>
+ <entry>test for TRUE, FALSE, UNKNOWN, NULL</entry>
</row>
<row>
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
/*----------
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
*
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",
* 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 $
*
*-------------------------------------------------------------------------
*/
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)
{
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;
* 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 $
*
*-------------------------------------------------------------------------
*/
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
*/
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;
* 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
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, " ");
_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'
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:
*
*
* 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
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
*
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);
*
*
* 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
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;
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:
{
*
*
* 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
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
%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 '^'
* (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);
}
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);
+ BooleanTest *b = makeNode(BooleanTest);
+ b->arg = $1;
+ b->booltesttype = IS_TRUE;
+ $$ = (Node *)b;
}
- | a_expr IS NOT FALSE_P
+ | a_expr IS NOT 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);
+ BooleanTest *b = makeNode(BooleanTest);
+ b->arg = $1;
+ b->booltesttype = IS_NOT_TRUE;
+ $$ = (Node *)b;
}
| 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_FALSE;
+ $$ = (Node *)b;
}
- | a_expr IS NOT TRUE_P
+ | a_expr IS NOT 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_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
{
| 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);
}
| TRUE_P { $$ = "true"; }
| UNION { $$ = "union"; }
| UNIQUE { $$ = "unique"; }
+ | UNKNOWN { $$ = "unknown"; }
| USER { $$ = "user"; }
| USING { $$ = "using"; }
| VACUUM { $$ = "vacuum"; }
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
{"type", TYPE_P},
{"union", UNION},
{"unique", UNIQUE},
+ {"unknown", UNKNOWN},
{"unlisten", UNLISTEN},
{"until", UNTIL},
{"update", UPDATE},
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
*/
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() */
/* 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;
/* 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;
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;
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
}
+/* 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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
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;
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);
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);
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");
/*
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.
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:
* 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.
*
#include "parser/parse_expr.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
-#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
}
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;
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
*/
* 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 $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200106161
+#define CATALOG_VERSION_NO 200106191
#endif
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
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)
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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,