diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2001-06-19 22:39:12 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2001-06-19 22:39:12 +0000 |
commit | 116d2bba7eeaf25c544bc187e3ad2a8677a9a22c (patch) | |
tree | c77a6b20a3acdbb6e25a1fc4a561c0e839ddbb1e /src/backend/parser | |
parent | 8c30aca2ba1a48d38b1206f8559d1dc6b65c5ca7 (diff) | |
download | postgresql-116d2bba7eeaf25c544bc187e3ad2a8677a9a22c.tar.gz postgresql-116d2bba7eeaf25c544bc187e3ad2a8677a9a22c.zip |
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.
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/gram.y | 135 | ||||
-rw-r--r-- | src/backend/parser/keywords.c | 3 | ||||
-rw-r--r-- | src/backend/parser/parse_clause.c | 32 | ||||
-rw-r--r-- | src/backend/parser/parse_coerce.c | 26 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 120 |
5 files changed, 214 insertions, 102 deletions
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 263830244dc..e47c3f0b331 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); + 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 { @@ -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 6064ca8a8ff..ccdfb88a2e2 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 02c6a4ac8c7..585b21b0f45 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 38f044217e5..283fd302407 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 a196779f44c..5fda57f5f92 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: |