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)

Reply via email to