Hi Zsolt, Tatsuo,
+ foreach_ptr(TargetEntry, te, defineClause)
> + (void) coerce_to_boolean(pstate, (Node *) te->expr, "DEFINE");
>
> Isn't this incorrect? I think it should update te->expr, as currently
> it is possible to construct queries where this produces unexpected
> results.
Good catch, Zsolt. You're right — the return value of
coerce_to_boolean() must be assigned back to te->expr, otherwise
any implicit cast (e.g., a user-defined type with an assignment
cast to boolean) is silently discarded.
The fix is straightforward:
foreach_ptr(TargetEntry, te, defineClause)
te->expr = (Expr *) coerce_to_boolean(pstate, (Node *) te->expr,
"DEFINE");
I've confirmed that your example query produces incorrect results
without the fix (the truthyint value is evaluated as-is without
the cast) and correct results with it.
Patch 13 is attached with the fix and a regression test case based
on your example.
By the way, thank you for joining the TDE hooks discussion when
I was just getting started as a contributor — that meant a lot.
Great work on pg_tde at Percona, and rooting for the extensible
SMGR effort as well. Hope it's all going well!
Tatsuo, please review when you get a chance.
Regards,
Henson
From e47fb8a119cb235665a9e128c8c1871836aff7b8 Mon Sep 17 00:00:00 2001
From: Henson Choi <[email protected]>
Date: Thu, 12 Mar 2026 08:41:21 +0900
Subject: [PATCH] Fix coerce_to_boolean result not applied in DEFINE clause
---
src/backend/parser/parse_rpr.c | 2 +-
src/test/regress/expected/rpr_base.out | 33 ++++++++++++++++++++++++++
src/test/regress/sql/rpr_base.sql | 29 ++++++++++++++++++++++
3 files changed, 63 insertions(+), 1 deletion(-)
diff --git a/src/backend/parser/parse_rpr.c b/src/backend/parser/parse_rpr.c
index dff91e439d2..e574b69d9b5 100644
--- a/src/backend/parser/parse_rpr.c
+++ b/src/backend/parser/parse_rpr.c
@@ -392,7 +392,7 @@ transformDefineClause(ParseState *pstate, WindowClause *wc,
WindowDef *windef,
* expression.
*/
foreach_ptr(TargetEntry, te, defineClause)
- (void) coerce_to_boolean(pstate, (Node *) te->expr, "DEFINE");
+ te->expr = (Expr *) coerce_to_boolean(pstate, (Node *)
te->expr, "DEFINE");
/* mark column origins */
markTargetListOrigins(pstate, defineClause);
diff --git a/src/test/regress/expected/rpr_base.out
b/src/test/regress/expected/rpr_base.out
index ffd0f8ed9f1..7931ad07d7d 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -285,6 +285,39 @@ ORDER BY id;
2 | 0
(2 rows)
+-- Implicit cast to boolean via custom type
+CREATE TYPE truthyint AS (v int);
+CREATE FUNCTION truthyint_to_bool(truthyint) RETURNS boolean AS $$
+ SELECT ($1).v <> 0;
+$$ LANGUAGE SQL IMMUTABLE STRICT;
+CREATE CAST (truthyint AS boolean)
+ WITH FUNCTION truthyint_to_bool(truthyint)
+ AS ASSIGNMENT;
+CREATE TABLE rpr_coerce (id int, val truthyint);
+INSERT INTO rpr_coerce VALUES (1, ROW(1)), (2, ROW(0)), (3, ROW(5)), (4,
ROW(0));
+SELECT id, val, cnt
+FROM (SELECT id, val,
+ COUNT(*) OVER w AS cnt
+ FROM rpr_coerce
+ WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+)
+ DEFINE A AS val
+ )
+) s ORDER BY id;
+ id | val | cnt
+----+-----+-----
+ 1 | (1) | 1
+ 2 | (0) | 0
+ 3 | (5) | 1
+ 4 | (0) | 0
+(4 rows)
+
+DROP TABLE rpr_coerce;
+DROP CAST (truthyint AS boolean);
+DROP FUNCTION truthyint_to_bool(truthyint);
+DROP TYPE truthyint;
DROP TABLE rpr_bool;
-- Complex expressions
CREATE TABLE rpr_complex (id INT, val1 INT, val2 INT);
diff --git a/src/test/regress/sql/rpr_base.sql
b/src/test/regress/sql/rpr_base.sql
index 3d6ca2bc2a2..f8815c7376a 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -231,6 +231,35 @@ WINDOW w AS (
)
ORDER BY id;
+-- Implicit cast to boolean via custom type
+CREATE TYPE truthyint AS (v int);
+CREATE FUNCTION truthyint_to_bool(truthyint) RETURNS boolean AS $$
+ SELECT ($1).v <> 0;
+$$ LANGUAGE SQL IMMUTABLE STRICT;
+CREATE CAST (truthyint AS boolean)
+ WITH FUNCTION truthyint_to_bool(truthyint)
+ AS ASSIGNMENT;
+
+CREATE TABLE rpr_coerce (id int, val truthyint);
+INSERT INTO rpr_coerce VALUES (1, ROW(1)), (2, ROW(0)), (3, ROW(5)), (4,
ROW(0));
+
+SELECT id, val, cnt
+FROM (SELECT id, val,
+ COUNT(*) OVER w AS cnt
+ FROM rpr_coerce
+ WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ PATTERN (A+)
+ DEFINE A AS val
+ )
+) s ORDER BY id;
+
+DROP TABLE rpr_coerce;
+DROP CAST (truthyint AS boolean);
+DROP FUNCTION truthyint_to_bool(truthyint);
+DROP TYPE truthyint;
+
DROP TABLE rpr_bool;
-- Complex expressions
--
2.50.1 (Apple Git-155)