BUG #19870: WinGetFuncArgInPartition(..., WINDOW_SEEK_TAIL, ...) returns wrong rows under IGNORE NULLS

From: PG Bug reporting form <noreply(at)postgresql(dot)org>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: 303677365(at)qq(dot)com
Subject: BUG #19870: WinGetFuncArgInPartition(..., WINDOW_SEEK_TAIL, ...) returns wrong rows under IGNORE NULLS
Date: 2026-11-11 08:48:00
Message-ID: 19870-a58b65793ce2a466@postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 19870
Logged by: chunling qin
Email address: 303677365(at)qq(dot)com
PostgreSQL version: 19rc1
Operating system: Linux x86_64; platform-independent (reproduced on
Description:

**Description:**

A user-defined C window function that calls the public API
`WinGetFuncArgInPartition(winobj, argno, relpos, WINDOW_SEEK_TAIL, ...)`
gets wrong rows when the call runs with `IGNORE NULLS`:

1. **Wrong results on data with NO NULLs** — where `IGNORE NULLS` must be a
no-op: with `seektype=TAIL, relpos=-2` the IGNORE result differs from the
RESPECT result for every row (5/5 diffs), and the returned values come from
the wrong rows. With `seektype=CURRENT` or `HEAD` the two agree (0 diffs),
so the defect is specific to the TAIL branch.
2. **Internal error for large positive relpos**: `relpos = INT_MAX` with
TAIL + IGNORE NULLS raises `ERROR: XX000: invalid memory alloc request size
1073741824` (it tries to expand the not-null bitmap for an out-of-range row
position), while TAIL + RESPECT NULLS correctly returns NULL. The server
stays up; only the statement fails.

Commit b8d82d17875 fixed wrong results of some built-in paths under IGNORE
NULLS, but this public-API TAIL branch still computes the starting row
position as if the not-null bitmap could be consulted there, and applies
relpos twice.

**Reproduction (minimal C window function + SQL; verified on master):**

C probe (compile with `cc -fPIC -shared -I"$(pg_config --includedir-server)"
-o /tmp/probe.so probe.c`):

```c
#include "postgres.h"
#include "fmgr.h"
#include "windowapi.h"

PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(win_partition_arg);

/* win_partition_arg(value, relpos int4, seektype int4) -> value
seektype: 0=CURRENT 1=HEAD 2=TAIL (WINDOW_SEEK_*) */
Datum
win_partition_arg(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
bool isnull = false;
bool isout = false;
int32 relpos;
int32 seektype;
Datum result;

WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);

relpos = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
seektype = DatumGetInt32(WinGetFuncArgCurrent(winobj, 2, &isnull));

result = WinGetFuncArgInPartition(winobj, 0, relpos, seektype,
false, &isnull, &isout);
if (isnull)
PG_RETURN_NULL();
PG_RETURN_DATUM(result);
}
```

SQL (5 rows, **no NULLs** — IGNORE NULLS must be a no-op here):

```sql
CREATE FUNCTION win_partition_arg(anyelement, int4, int4) RETURNS anyelement
AS '/tmp/probe.so', 'win_partition_arg' LANGUAGE C WINDOW;

CREATE TABLE b1_txt (k int, x text);
INSERT INTO b1_txt VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e');

-- (1) diffs per seektype (RESPECT vs IGNORE must be 0 everywhere on no-NULL
data):
SELECT 'CURRENT rp=2' AS seektype, count(*) FILTER (WHERE r IS DISTINCT FROM
i) AS diffs
FROM (SELECT win_partition_arg(x,2,0) RESPECT NULLS OVER w AS r,
win_partition_arg(x,2,0) IGNORE NULLS OVER w AS i
FROM b1_txt WINDOW w AS (ORDER BY k)) s
UNION ALL
SELECT 'HEAD rp=2', count(*) FILTER (WHERE r IS DISTINCT FROM i)
FROM (SELECT win_partition_arg(x,2,1) RESPECT NULLS OVER w AS r,
win_partition_arg(x,2,1) IGNORE NULLS OVER w AS i
FROM b1_txt WINDOW w AS (ORDER BY k)) s
UNION ALL
SELECT 'TAIL rp=-2', count(*) FILTER (WHERE r IS DISTINCT FROM i)
FROM (SELECT win_partition_arg(x,-2,2) RESPECT NULLS OVER w AS r,
win_partition_arg(x,-2,2) IGNORE NULLS OVER w AS i
FROM b1_txt WINDOW w AS (ORDER BY k)) s;
-- CURRENT rp=2 | 0
-- HEAD rp=2 | 0
-- TAIL rp=-2 | 5 <- bug: every row wrong

-- (2) the actual wrong values (TAIL, relpos=-1):
SELECT k, win_partition_arg(x,-1,2) RESPECT NULLS OVER w AS respect,
win_partition_arg(x,-1,2) IGNORE NULLS OVER w AS ignore
FROM b1_txt WINDOW w AS (ORDER BY k) ORDER BY k;
-- 1..5: respect = d on every row; ignore = c on every row (wrong rows
returned)

-- (3) large positive relpos, TAIL:
SELECT win_partition_arg(x,2147483647,2) RESPECT NULLS OVER w FROM b1_txt
WINDOW w AS (ORDER BY k) LIMIT 1;
-- NULL (correct)
SELECT win_partition_arg(x,2147483647,2) IGNORE NULLS OVER w FROM b1_txt
WINDOW w AS (ORDER BY k) LIMIT 1;
-- ERROR: XX000: invalid memory alloc request size 1073741824 (should
return NULL)
```

**Expected behavior:** on data with no NULLs, `IGNORE NULLS` must return
exactly what `RESPECT NULLS` returns for every seektype; and an out-of-range
relpos must yield NULL, not an internal error — as already happens for
CURRENT/HEAD.

**Related:** b8d82d17875 (fixed some built-in IGNORE NULLS paths) and
69b20fdd20d (fixed the first_value/nth_value infinite loop) — this TAIL
branch of the public API is the remaining face of the same family.

Browse pgsql-bugs by date

  From Date Subject
Next Message PG Bug reporting form 2026-11-11 08:53:50 BUG #19871: JSON_TABLE evaluates each PASSING argument once per column - a named variable $v gets different valu
Previous Message PG Bug reporting form 2026-11-11 08:34:31 BUG #19869: Wrong results: planner silently drops the second ORDER BY sort key when both keys reference the same