From 4aaba1edcb64a70eb3ca1eeac63f6c5288df1d8b Mon Sep 17 00:00:00 2001 From: Vik Fearing Date: Thu, 10 Sep 2026 14:31:03 +0200 Subject: [PATCH v3 2/3] Add the IMPLIES boolean operator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit a IMPLIES b is material implication, parsed as a non-associative operator binding below OR and expanded to (NOT a OR b) in the parser, so type errors are reported against IMPLIES itself. Because NOT and OR are already Kleene operators, that expansion settles the three-valued truth table on Kleene implication, where Unknown IMPLIES Unknown is Unknown. SQL has until now used only the fragment of three-valued logic on which Kleene and Łukasiewicz agree, and implication is exactly where the two differ: Łukasiewicz would make Unknown IMPLIES Unknown be True, which could not then be written as NOT a OR b. IMPLIES is non-associative, so a IMPLIES b IMPLIES c is a syntax error and the intended grouping has to be parenthesized. The two groupings are different formulas, there is no consensus on which one a chain ought to mean, and the grammar declines to choose, just as it already does for a = b = c. Nothing is foreclosed by that: if the standard later gives IMPLIES an associativity, accepting chains is a one-line change, and no query that is accepted today changes meaning. --- doc/src/sgml/func/func-logical.sgml | 98 +++++++++++++++++ doc/src/sgml/syntax.sgml | 6 ++ src/backend/nodes/outfuncs.c | 4 + src/backend/nodes/readfuncs.c | 5 + src/backend/parser/gram.y | 11 +- src/backend/parser/parse_expr.c | 35 ++++++ src/include/nodes/parsenodes.h | 1 + src/include/parser/kwlist.h | 1 + src/test/regress/expected/boolean.out | 147 ++++++++++++++++++++++++++ src/test/regress/sql/boolean.sql | 60 +++++++++++ 10 files changed, 367 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/func/func-logical.sgml b/doc/src/sgml/func/func-logical.sgml index 5ba6389db56..0f3c6feb295 100644 --- a/doc/src/sgml/func/func-logical.sgml +++ b/doc/src/sgml/func/func-logical.sgml @@ -32,24 +32,33 @@ disjunction negation + + IMPLIES + + + + implication + + boolean AND boolean boolean boolean OR boolean boolean NOT boolean boolean +boolean IMPLIES boolean boolean SQL uses a three-valued logic system with true, false, and unknown; the null value of a boolean and the truth value unknown are one and the same. In the truth tables below the left operand of a binary operator selects the row, and the right operand the column: @@ -139,19 +148,108 @@ False True Unknown + + The IMPLIES operator is material implication: + a IMPLIES + b is equivalent to NOT + a OR + b, and reads as if + a then b. + Unlike AND and OR it is not + commutative, which is why its table, unlike theirs, is not symmetric + about the diagonal: + + + + + + IMPLIES + True + False + Unknown + + + + + + True + True + False + Unknown + + + + False + True + True + True + + + + Unknown + True + Unknown + Unknown + + + + + + + + IMPLIES binds less tightly than every other operator, + OR included, so + a = 1 IMPLIES b = 2 OR c = 3 means + (a = 1) IMPLIES ((b = 2) OR (c = 3)). See . + + + + Implication is not associative, and IMPLIES therefore + does not chain: a IMPLIES + b IMPLIES + c is a syntax error, just as + a = b = c is. The grouping has to be written + explicitly, and the two groupings mean different things: + (a IMPLIES + b) IMPLIES + c is not equivalent to + a IMPLIES + (b IMPLIES + c). + + + + Of the two, grouping to the right is usually the one wanted: it is the + same as a single implication whose antecedent is the conjunction of the + two leading operands, so a + IMPLIES (b + IMPLIES c) is equivalent to + (a AND + b) IMPLIES + c, that is, if both + a and b, then + c. To say if + a, then both b and + c instead, write + a IMPLIES + (b AND + c). + + The operators AND and OR are commutative, that is, you can switch the left and right operands without affecting the result. (However, it is not guaranteed that the left operand is evaluated before the right operand. See for more information about the order of evaluation of subexpressions.) diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 67482996861..d0887e8677f 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -1096,20 +1096,26 @@ CAST ( 'string' AS type ) AND left logical conjunction OR left logical disjunction + + + IMPLIES + + logical implication + Note that the operator precedence rules also apply to user-defined operators that have the same names as the built-in operators mentioned above. For example, if you define a + operator for some custom data type it will have the same precedence as the built-in + operator, no diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 40990143927..9a35ff53de0 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -636,20 +636,24 @@ _outA_Expr(StringInfo str, const A_Expr *node) WRITE_NODE_FIELD(name); break; case AEXPR_BETWEEN_SYM: appendStringInfoString(str, " BETWEEN_SYM"); WRITE_NODE_FIELD(name); break; case AEXPR_NOT_BETWEEN_SYM: appendStringInfoString(str, " NOT_BETWEEN_SYM"); WRITE_NODE_FIELD(name); break; + case AEXPR_IMPLIES: + appendStringInfoString(str, " IMPLIES"); + WRITE_NODE_FIELD(name); + break; default: elog(ERROR, "unrecognized A_Expr_Kind: %d", (int) node->kind); break; } WRITE_NODE_FIELD(lexpr); WRITE_NODE_FIELD(rexpr); WRITE_LOCATION_FIELD(rexpr_list_start); WRITE_LOCATION_FIELD(rexpr_list_end); WRITE_LOCATION_FIELD(location); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 2839a711f9e..4cc019a012b 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -507,20 +507,25 @@ _readA_Expr(ReadNodeContext *ctx) else if (length == 11 && strncmp(token, "BETWEEN_SYM", 11) == 0) { local_node->kind = AEXPR_BETWEEN_SYM; READ_NODE_FIELD(name); } else if (length == 15 && strncmp(token, "NOT_BETWEEN_SYM", 15) == 0) { local_node->kind = AEXPR_NOT_BETWEEN_SYM; READ_NODE_FIELD(name); } + else if (length == 7 && strncmp(token, "IMPLIES", 7) == 0) + { + local_node->kind = AEXPR_IMPLIES; + READ_NODE_FIELD(name); + } else if (length == 5 && strncmp(token, ":name", 5) == 0) { local_node->kind = AEXPR_OP; local_node->name = nodeRead(ctx, NULL, 0); } else elog(ERROR, "unrecognized A_Expr kind: \"%.*s\"", length, token); READ_NODE_FIELD(lexpr); READ_NODE_FIELD(rexpr); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0563453fe24..73c3b1f4943 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -742,21 +742,22 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS HANDLER HAVING HEADER_P HOLD HOUR_P - IDENTITY_P IF_P IGNORE_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE + IDENTITY_P IF_P IGNORE_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPLIES + IMPORT_P IN_P INCLUDE INCLUDING INCREMENT INDENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_TABLE JSON_VALUE KEEP KEY KEYS LABEL LANGUAGE LARGE_P LAST_P LATERAL_P @@ -837,20 +838,21 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %token MODE_TYPE_NAME %token MODE_PLPGSQL_EXPR %token MODE_PLPGSQL_ASSIGN1 %token MODE_PLPGSQL_ASSIGN2 %token MODE_PLPGSQL_ASSIGN3 /* Precedence: lowest to highest */ %left UNION EXCEPT %left INTERSECT +%nonassoc IMPLIES %left OR %left AND %right NOT %nonassoc IS ISNULL NOTNULL /* IS sets precedence for IS NULL, etc */ %nonassoc '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS %nonassoc BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA %nonassoc ESCAPE /* ESCAPE must be just above LIKE/ILIKE/SIMILAR */ /* * Sometimes it is necessary to assign precedence to keywords that are not @@ -15367,20 +15369,25 @@ a_expr: c_expr { $$ = $1; } | a_expr qual_Op a_expr %prec Op { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); } | qual_Op a_expr %prec Op { $$ = (Node *) makeA_Expr(AEXPR_OP, $1, NULL, $2, @1); } | a_expr AND a_expr { $$ = makeAndExpr($1, $3, @2); } | a_expr OR a_expr { $$ = makeOrExpr($1, $3, @2); } + | a_expr IMPLIES a_expr + { + $$ = (Node *) makeSimpleA_Expr(AEXPR_IMPLIES, "IMPLIES", + $1, $3, @2); + } | NOT a_expr { $$ = makeNotExpr($2, @1); } | NOT_LA a_expr %prec NOT { $$ = makeNotExpr($2, @1); } | a_expr LIKE a_expr { $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~", $1, $3, @2); } @@ -18183,20 +18190,21 @@ unreserved_keyword: | HANDLER | HEADER_P | HOLD | HOUR_P | IDENTITY_P | IF_P | IGNORE_P | IMMEDIATE | IMMUTABLE | IMPLICIT_P + | IMPLIES | IMPORT_P | INCLUDE | INCLUDING | INCREMENT | INDENT | INDEX | INDEXES | INHERIT | INHERITS | INLINE_P @@ -18775,20 +18783,21 @@ bare_label_keyword: | GROUPS | HANDLER | HEADER_P | HOLD | IDENTITY_P | IF_P | ILIKE | IMMEDIATE | IMMUTABLE | IMPLICIT_P + | IMPLIES | IMPORT_P | IN_P | INCLUDE | INCLUDING | INCREMENT | INDENT | INDEX | INDEXES | INHERIT | INHERITS diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 05a6b72d4c9..3f9dc86fa00 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -47,20 +47,21 @@ bool Transform_null_equals = false; static Node *transformExprRecurse(ParseState *pstate, Node *expr); static Node *transformParamRef(ParseState *pstate, ParamRef *pref); static Node *transformAExprOp(ParseState *pstate, A_Expr *a); static Node *transformAExprOpAny(ParseState *pstate, A_Expr *a); static Node *transformAExprOpAll(ParseState *pstate, A_Expr *a); static Node *transformAExprDistinct(ParseState *pstate, A_Expr *a); static Node *transformAExprNullIf(ParseState *pstate, A_Expr *a); static Node *transformAExprIn(ParseState *pstate, A_Expr *a); static Node *transformAExprBetween(ParseState *pstate, A_Expr *a); +static Node *transformAExprImplies(ParseState *pstate, A_Expr *a); static Node *transformMergeSupportFunc(ParseState *pstate, MergeSupportFunc *f); static Node *transformBoolExpr(ParseState *pstate, BoolExpr *a); static Node *transformFuncCall(ParseState *pstate, FuncCall *fn); static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref); static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c); static Node *transformSubLink(ParseState *pstate, SubLink *sublink); static Node *transformArrayExpr(ParseState *pstate, A_ArrayExpr *a, Oid array_type, Oid element_type, int32 typmod); static Node *transformRowExpr(ParseState *pstate, RowExpr *r, bool allowDefault); static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c); @@ -206,20 +207,23 @@ transformExprRecurse(ParseState *pstate, Node *expr) case AEXPR_SIMILAR: /* we can transform these just like AEXPR_OP */ result = transformAExprOp(pstate, a); break; case AEXPR_BETWEEN: case AEXPR_NOT_BETWEEN: case AEXPR_BETWEEN_SYM: case AEXPR_NOT_BETWEEN_SYM: result = transformAExprBetween(pstate, a); break; + case AEXPR_IMPLIES: + result = transformAExprImplies(pstate, a); + break; default: elog(ERROR, "unrecognized A_Expr kind: %d", a->kind); result = NULL; /* keep compiler quiet */ break; } break; } case T_BoolExpr: result = transformBoolExpr(pstate, (BoolExpr *) expr); @@ -1439,20 +1443,51 @@ transformBoolExpr(ParseState *pstate, BoolExpr *a) Node *arg = (Node *) lfirst(lc); arg = transformExprRecurse(pstate, arg); arg = coerce_to_boolean(pstate, arg, opname); args = lappend(args, arg); } return (Node *) makeBoolExpr(a->boolop, args, a->location); } +/* + * Transform "a IMPLIES b" into the equivalent "NOT a OR b". + * + * We expand this here rather than in gram.y so that a non-boolean operand is + * complained of in terms of IMPLIES, rather than in terms of the NOT or OR + * that the construct happens to be built from. + */ +static Node * +transformAExprImplies(ParseState *pstate, A_Expr *a) +{ + Node *lexpr; + Node *rexpr; + + lexpr = transformExprRecurse(pstate, a->lexpr); + rexpr = transformExprRecurse(pstate, a->rexpr); + + lexpr = coerce_to_boolean(pstate, lexpr, "IMPLIES"); + rexpr = coerce_to_boolean(pstate, rexpr, "IMPLIES"); + + /* + * Each operand appears exactly once in the expansion, so unlike BETWEEN + * this does not risk evaluating anything twice. + */ + return (Node *) makeBoolExpr(OR_EXPR, + list_make2(makeBoolExpr(NOT_EXPR, + list_make1(lexpr), + exprLocation(lexpr)), + rexpr), + a->location); +} + static Node * transformFuncCall(ParseState *pstate, FuncCall *fn) { Node *last_srf = pstate->p_last_srf; List *targs; ListCell *args; /* Transform the list of arguments ... */ targs = NIL; foreach(args, fn->args) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 0debcd193ab..45fa8c70263 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -335,20 +335,21 @@ typedef enum A_Expr_Kind AEXPR_NOT_DISTINCT, /* IS NOT DISTINCT FROM - name must be "=" */ AEXPR_NULLIF, /* NULLIF - name must be "=" */ AEXPR_IN, /* [NOT] IN - name must be "=" or "<>" */ AEXPR_LIKE, /* [NOT] LIKE - name must be "~~" or "!~~" */ AEXPR_ILIKE, /* [NOT] ILIKE - name must be "~~*" or "!~~*" */ AEXPR_SIMILAR, /* [NOT] SIMILAR - name must be "~" or "!~" */ AEXPR_BETWEEN, /* name must be "BETWEEN" */ AEXPR_NOT_BETWEEN, /* name must be "NOT BETWEEN" */ AEXPR_BETWEEN_SYM, /* name must be "BETWEEN SYMMETRIC" */ AEXPR_NOT_BETWEEN_SYM, /* name must be "NOT BETWEEN SYMMETRIC" */ + AEXPR_IMPLIES, /* name must be "IMPLIES" */ } A_Expr_Kind; typedef struct A_Expr { pg_node_attr(custom_read_write) NodeTag type; A_Expr_Kind kind; /* see above */ List *name; /* possibly-qualified name of operator */ Node *lexpr; /* left argument, or NULL if none */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 53ae96c0399..94d2aef1bb1 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -200,20 +200,21 @@ PG_KEYWORD("having", HAVING, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("hour", HOUR_P, UNRESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("identity", IDENTITY_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("if", IF_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("ignore", IGNORE_P, UNRESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("immediate", IMMEDIATE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("implies", IMPLIES, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("indent", INDENT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/src/test/regress/expected/boolean.out b/src/test/regress/expected/boolean.out index 0e99eb7ffc0..5c092b09a5e 100644 --- a/src/test/regress/expected/boolean.out +++ b/src/test/regress/expected/boolean.out @@ -559,20 +559,167 @@ SELECT istrue OR isfalse OR isnul FROM booltbl4; ---------- t (1 row) SELECT isnul OR istrue OR isfalse FROM booltbl4; ?column? ---------- t (1 row) +-- Implication: "a IMPLIES b" is "NOT a OR b". It is not commutative, so all +-- nine combinations of three-valued logic have to be checked. +SELECT istrue IMPLIES istrue, istrue IMPLIES isfalse, istrue IMPLIES isnul + FROM booltbl4; + ?column? | ?column? | ?column? +----------+----------+---------- + t | f | (null) +(1 row) + +SELECT isfalse IMPLIES istrue, isfalse IMPLIES isfalse, isfalse IMPLIES isnul + FROM booltbl4; + ?column? | ?column? | ?column? +----------+----------+---------- + t | t | t +(1 row) + +SELECT isnul IMPLIES istrue, isnul IMPLIES isfalse, isnul IMPLIES isnul + FROM booltbl4; + ?column? | ?column? | ?column? +----------+----------+---------- + t | (null) | (null) +(1 row) + +-- the same, as constants, so that constant folding is exercised too +SELECT true IMPLIES true, true IMPLIES false, true IMPLIES null; + ?column? | ?column? | ?column? +----------+----------+---------- + t | f | (null) +(1 row) + +SELECT false IMPLIES true, false IMPLIES false, false IMPLIES null; + ?column? | ?column? | ?column? +----------+----------+---------- + t | t | t +(1 row) + +SELECT null IMPLIES true, null IMPLIES false, null::bool IMPLIES null; + ?column? | ?column? | ?column? +----------+----------+---------- + t | (null) | (null) +(1 row) + +-- IMPLIES is non-associative, so a chain is refused rather than grouped +SELECT isfalse IMPLIES istrue IMPLIES isfalse FROM booltbl4; -- error +ERROR: syntax error at or near "IMPLIES" +LINE 1: SELECT isfalse IMPLIES istrue IMPLIES isfalse FROM booltbl4; + ^ +-- and it has to be, because the two groupings are different formulas. The +-- first row below is the interesting one: it tells them apart. +SELECT a, b, c, + (a IMPLIES b) IMPLIES c AS grouped_left, + a IMPLIES (b IMPLIES c) AS grouped_right, + (a AND b) IMPLIES c AS conj_antecedent, + a IMPLIES (b AND c) AS conj_consequent + FROM (VALUES (false, false, false), + (true, false, false), + (true, true, true)) AS t(a, b, c); + a | b | c | grouped_left | grouped_right | conj_antecedent | conj_consequent +---+---+---+--------------+---------------+-----------------+----------------- + f | f | f | f | t | t | t + t | f | f | t | t | t | f + t | t | t | t | t | t | t +(3 rows) + +-- grouping to the right is implication from the conjunction of the operands +-- (exportation), which holds for all three truth values, so no rows here +SELECT a, b, c + FROM (VALUES (true), (false), (null)) AS x(a), + (VALUES (true), (false), (null)) AS y(b), + (VALUES (true), (false), (null)) AS z(c) + WHERE (a IMPLIES (b IMPLIES c)) IS DISTINCT FROM ((a AND b) IMPLIES c); + a | b | c +---+---+--- +(0 rows) + +-- and binds looser than OR, AND, NOT, the comparison operators and IS +SELECT istrue OR isfalse IMPLIES isfalse FROM booltbl4; + ?column? +---------- + f +(1 row) + +SELECT isfalse IMPLIES isfalse AND isfalse FROM booltbl4; + ?column? +---------- + t +(1 row) + +SELECT NOT istrue IMPLIES istrue FROM booltbl4; + ?column? +---------- + t +(1 row) + +SELECT 1 = 1 IMPLIES 2 = 3; + ?column? +---------- + f +(1 row) + +SELECT isfalse IMPLIES isnul IS NULL FROM booltbl4; + ?column? +---------- + t +(1 row) + +-- non-boolean operands are reported in terms of IMPLIES, not of its expansion +SELECT 1 IMPLIES true; -- error +ERROR: argument of IMPLIES must be type boolean, not type integer +LINE 1: SELECT 1 IMPLIES true; + ^ +SELECT true IMPLIES 1; -- error +ERROR: argument of IMPLIES must be type boolean, not type integer +LINE 1: SELECT true IMPLIES 1; + ^ +-- the construct is expanded during parse analysis, so this is what is stored +CREATE VIEW boolview AS SELECT istrue IMPLIES isnul AS i FROM booltbl4; +SELECT pg_get_viewdef('boolview', true); + pg_get_viewdef +---------------------------------- + SELECT NOT istrue OR isnul AS i+ + FROM booltbl4; +(1 row) + +DROP VIEW boolview; +-- IMPLIES is unreserved, so it remains usable as an identifier +CREATE TABLE implies (implies bool); +INSERT INTO implies VALUES (false); +SELECT implies IMPLIES implies FROM implies; + ?column? +---------- + t +(1 row) + +DROP TABLE implies; +SELECT 1 AS implies; + implies +--------- + 1 +(1 row) + +SELECT 1 implies; + implies +--------- + 1 +(1 row) + -- Casts SELECT 0::boolean; bool ------ f (1 row) SELECT 1::boolean; bool ------ diff --git a/src/test/regress/sql/boolean.sql b/src/test/regress/sql/boolean.sql index 85c6b019882..dfe96cb8ae5 100644 --- a/src/test/regress/sql/boolean.sql +++ b/src/test/regress/sql/boolean.sql @@ -243,20 +243,80 @@ SELECT isnul AND istrue AND isfalse FROM booltbl4; -- OR expression need to return null if there's any nulls and none -- of the value is true SELECT isfalse OR isnul OR isfalse FROM booltbl4; SELECT isfalse OR isfalse OR isnul FROM booltbl4; SELECT isnul OR isfalse OR isfalse FROM booltbl4; SELECT isfalse OR isnul OR istrue FROM booltbl4; SELECT istrue OR isfalse OR isnul FROM booltbl4; SELECT isnul OR istrue OR isfalse FROM booltbl4; +-- Implication: "a IMPLIES b" is "NOT a OR b". It is not commutative, so all +-- nine combinations of three-valued logic have to be checked. +SELECT istrue IMPLIES istrue, istrue IMPLIES isfalse, istrue IMPLIES isnul + FROM booltbl4; +SELECT isfalse IMPLIES istrue, isfalse IMPLIES isfalse, isfalse IMPLIES isnul + FROM booltbl4; +SELECT isnul IMPLIES istrue, isnul IMPLIES isfalse, isnul IMPLIES isnul + FROM booltbl4; + +-- the same, as constants, so that constant folding is exercised too +SELECT true IMPLIES true, true IMPLIES false, true IMPLIES null; +SELECT false IMPLIES true, false IMPLIES false, false IMPLIES null; +SELECT null IMPLIES true, null IMPLIES false, null::bool IMPLIES null; + +-- IMPLIES is non-associative, so a chain is refused rather than grouped +SELECT isfalse IMPLIES istrue IMPLIES isfalse FROM booltbl4; -- error + +-- and it has to be, because the two groupings are different formulas. The +-- first row below is the interesting one: it tells them apart. +SELECT a, b, c, + (a IMPLIES b) IMPLIES c AS grouped_left, + a IMPLIES (b IMPLIES c) AS grouped_right, + (a AND b) IMPLIES c AS conj_antecedent, + a IMPLIES (b AND c) AS conj_consequent + FROM (VALUES (false, false, false), + (true, false, false), + (true, true, true)) AS t(a, b, c); + +-- grouping to the right is implication from the conjunction of the operands +-- (exportation), which holds for all three truth values, so no rows here +SELECT a, b, c + FROM (VALUES (true), (false), (null)) AS x(a), + (VALUES (true), (false), (null)) AS y(b), + (VALUES (true), (false), (null)) AS z(c) + WHERE (a IMPLIES (b IMPLIES c)) IS DISTINCT FROM ((a AND b) IMPLIES c); + +-- and binds looser than OR, AND, NOT, the comparison operators and IS +SELECT istrue OR isfalse IMPLIES isfalse FROM booltbl4; +SELECT isfalse IMPLIES isfalse AND isfalse FROM booltbl4; +SELECT NOT istrue IMPLIES istrue FROM booltbl4; +SELECT 1 = 1 IMPLIES 2 = 3; +SELECT isfalse IMPLIES isnul IS NULL FROM booltbl4; + +-- non-boolean operands are reported in terms of IMPLIES, not of its expansion +SELECT 1 IMPLIES true; -- error +SELECT true IMPLIES 1; -- error + +-- the construct is expanded during parse analysis, so this is what is stored +CREATE VIEW boolview AS SELECT istrue IMPLIES isnul AS i FROM booltbl4; +SELECT pg_get_viewdef('boolview', true); +DROP VIEW boolview; + +-- IMPLIES is unreserved, so it remains usable as an identifier +CREATE TABLE implies (implies bool); +INSERT INTO implies VALUES (false); +SELECT implies IMPLIES implies FROM implies; +DROP TABLE implies; +SELECT 1 AS implies; +SELECT 1 implies; + -- Casts SELECT 0::boolean; SELECT 1::boolean; SELECT 2::boolean; -- -- Clean up -- Many tables are retained by the regression test, but these do not seem -- particularly useful so just get rid of them for now. -- 2.56.0