diff -Naur pgsql.virg/src/backend/executor/execQual.c pgsql.dev/src/backend/executor/execQual.c --- pgsql.virg/src/backend/executor/execQual.c Sat Jun 16 18:56:42 2001 +++ pgsql.dev/src/backend/executor/execQual.c Mon Jun 18 06:15:59 2001 @@ -62,7 +62,8 @@ 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); /*---------- * ExecEvalArrayRef @@ -1092,6 +1093,46 @@ } /* ---------------------------------------------------------------- + * 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); + if (*isNull && ntest->nulltesttype == IS_NULL) + { + *isNull = FALSE; + return BoolGetDatum(TRUE); + } + else if (*isNull && ntest->nulltesttype == IS_NOT_NULL) + { + *isNull = FALSE; + return BoolGetDatum(FALSE); + } + else if (!*isNull && ntest->nulltesttype == IS_NULL) + { + return BoolGetDatum(FALSE); + } + else if (!*isNull && ntest->nulltesttype == IS_NOT_NULL) + { + return BoolGetDatum(TRUE); + } + else /* should never get here */ + { + elog(ERROR, "NullTest node: something unexpected happened!"); + return BoolGetDatum(NULL); + } +} + +/* ---------------------------------------------------------------- * ExecEvalFieldSelect * * Evaluate a FieldSelect node. @@ -1262,6 +1303,12 @@ break; case T_CaseExpr: retDatum = ExecEvalCase((CaseExpr *) expression, + econtext, + isNull, + isDone); + break; + case T_NullTest: + retDatum = ExecEvalNullTest((NullTest *) expression, econtext, isNull, isDone); diff -Naur pgsql.virg/src/backend/nodes/copyfuncs.c pgsql.dev/src/backend/nodes/copyfuncs.c --- pgsql.virg/src/backend/nodes/copyfuncs.c Sat Jun 16 18:56:45 2001 +++ pgsql.dev/src/backend/nodes/copyfuncs.c Mon Jun 18 05:11:36 2001 @@ -1017,6 +1017,24 @@ 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; +} + static ArrayRef * _copyArrayRef(ArrayRef *from) { @@ -2953,6 +2971,9 @@ break; case T_CaseWhen: retval = _copyCaseWhen(from); + break; + case T_NullTest: + retval = _copyNullTest(from); break; case T_FkConstraint: retval = _copyFkConstraint(from); diff -Naur pgsql.virg/src/backend/nodes/equalfuncs.c pgsql.dev/src/backend/nodes/equalfuncs.c --- pgsql.virg/src/backend/nodes/equalfuncs.c Sat Jun 16 18:56:45 2001 +++ pgsql.dev/src/backend/nodes/equalfuncs.c Mon Jun 18 04:57:58 2001 @@ -1712,6 +1712,16 @@ 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; +} + /* * Stuff from pg_list.h */ @@ -2119,6 +2129,9 @@ break; case T_CaseWhen: retval = _equalCaseWhen(a, b); + break; + case T_NullTest: + retval = _equalNullTest(a, b); break; case T_FkConstraint: retval = _equalFkConstraint(a, b); diff -Naur pgsql.virg/src/backend/nodes/outfuncs.c pgsql.dev/src/backend/nodes/outfuncs.c --- pgsql.virg/src/backend/nodes/outfuncs.c Sat Jun 16 18:56:45 2001 +++ pgsql.dev/src/backend/nodes/outfuncs.c Mon Jun 18 04:55:49 2001 @@ -1403,6 +1403,19 @@ } /* + * NullTest + */ +static void +_outNullTest(StringInfo str, NullTest *node) +{ + appendStringInfo(str, " NULLTEST :arg "); + _outNode(str, node->arg); + + appendStringInfo(str, " :nulltesttype %d ", + node->nulltesttype); +} + +/* * _outNode - * converts a Node into ascii string and append it to 'str' */ @@ -1639,7 +1652,9 @@ case T_CaseWhen: _outCaseWhen(str, obj); break; - + case T_NullTest: + _outNullTest(str, obj); + break; case T_VariableSetStmt: break; case T_SelectStmt: diff -Naur pgsql.virg/src/backend/nodes/readfuncs.c pgsql.dev/src/backend/nodes/readfuncs.c --- pgsql.virg/src/backend/nodes/readfuncs.c Sat Jun 16 18:56:45 2001 +++ pgsql.dev/src/backend/nodes/readfuncs.c Mon Jun 18 04:54:06 2001 @@ -860,6 +860,31 @@ } /* ---------------- + * _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; +} + +/* ---------------- * _readVar * * Var is a subclass of Expr @@ -1966,6 +1991,8 @@ 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 elog(ERROR, "badly formatted planstring \"%.10s\"...", token); diff -Naur pgsql.virg/src/backend/optimizer/util/clauses.c pgsql.dev/src/backend/optimizer/util/clauses.c --- pgsql.virg/src/backend/optimizer/util/clauses.c Sat Jun 16 18:56:46 2001 +++ pgsql.dev/src/backend/optimizer/util/clauses.c Mon Jun 18 04:50:10 2001 @@ -1580,6 +1580,8 @@ return true; } break; + case T_NullTest: + return walker(((NullTest *) node)->arg, context); case T_SubLink: { SubLink *sublink = (SubLink *) node; @@ -1930,6 +1932,16 @@ FLATCOPY(newnode, casewhen, CaseWhen); MUTATE(newnode->expr, casewhen->expr, Node *); MUTATE(newnode->result, casewhen->result, 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; diff -Naur pgsql.virg/src/backend/parser/gram.y pgsql.dev/src/backend/parser/gram.y --- pgsql.virg/src/backend/parser/gram.y Sat Jun 16 18:56:47 2001 +++ pgsql.dev/src/backend/parser/gram.y Mon Jun 18 05:25:02 2001 @@ -4434,9 +4434,19 @@ * (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,15 +4509,43 @@ n->agg_distinct = FALSE; $$ = makeA_Expr(OP, "!~~*", $1, (Node *) n); } - + /* NullTest clause + * Define SQL92-style Null test clause. + * Allow all two forms described in the standard: + * a IS NULL + * a IS NOT NULL + * Allow two SQL extensions + * a ISNULL + * a NOTNULL + */ | 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. diff -Naur pgsql.virg/src/backend/parser/parse_expr.c pgsql.dev/src/backend/parser/parse_expr.c --- pgsql.virg/src/backend/parser/parse_expr.c Sat Jun 16 18:56:47 2001 +++ pgsql.dev/src/backend/parser/parse_expr.c Mon Jun 18 05:10:49 2001 @@ -510,6 +510,16 @@ break; } + case T_NullTest: + { + NullTest *n = (NullTest *) expr; + + n->arg = transformExpr(pstate, n->arg, precedence); + + result = (Node *) expr; + break; + } + /* * Quietly accept node types that may be presented when we are * called on an already-transformed tree. @@ -668,6 +678,9 @@ break; case T_CaseWhen: type = exprType(((CaseWhen *) expr)->result); + break; + case T_NullTest: + type = BOOLOID; break; case T_Ident: /* is this right? */ diff -Naur pgsql.virg/src/backend/utils/adt/ruleutils.c pgsql.dev/src/backend/utils/adt/ruleutils.c --- pgsql.virg/src/backend/utils/adt/ruleutils.c Sat Jun 16 18:56:48 2001 +++ pgsql.dev/src/backend/utils/adt/ruleutils.c Sat Jun 16 23:36:15 2001 @@ -1891,6 +1891,21 @@ } break; + case T_NullTest: + { + NullTest *ntest = (NullTest *) node; + + get_rule_expr(ntest->arg, context); + if (ntest->nulltesttype == IS_NULL) + appendStringInfo(buf, "%s", " IS NULL "); + else if (ntest->nulltesttype == IS_NOT_NULL) + appendStringInfo(buf, "%s", " IS NOT NULL "); + else /* should never happen */ + elog(ERROR, "invalid NullTest type %d", + ntest->nulltesttype); + } + break; + case T_RelabelType: { RelabelType *relabel = (RelabelType *) node; diff -Naur pgsql.virg/src/include/nodes/nodes.h pgsql.dev/src/include/nodes/nodes.h --- pgsql.virg/src/include/nodes/nodes.h Sat Jun 16 18:56:52 2001 +++ pgsql.dev/src/include/nodes/nodes.h Mon Jun 18 04:35:25 2001 @@ -225,6 +225,7 @@ T_RowMarkXXX, /* not used anymore; tag# available */ T_FkConstraint, T_PrivGrantee, + T_NullTest, /* * TAGS FOR FUNCTION-CALL CONTEXT AND RESULTINFO NODES (see fmgr.h) diff -Naur pgsql.virg/src/include/nodes/parsenodes.h pgsql.dev/src/include/nodes/parsenodes.h --- pgsql.virg/src/include/nodes/parsenodes.h Sat Jun 16 18:56:52 2001 +++ pgsql.dev/src/include/nodes/parsenodes.h Mon Jun 18 04:39:00 2001 @@ -1054,6 +1054,28 @@ Node *result; /* substitution result */ } CaseWhen; +/* ---------------- + * NullTest + * + * NullTest represents the operation of comparing a value to a NULL + * value. At runtime, the input expression is expected to be IS NULL, + * IS NOT NULL, ISNULL, or NOTNULL. + * The appropriate test is performed and returned as a 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; + /* * ColumnDef - column definition (used in various creates) *