Re: Assertion failure when importing expression statistics with nested JSON values

From: Chao Li <li(dot)evan(dot)chao(at)gmail(dot)com>
To: Ilia Evdokimov <ilya(dot)evdokimov(at)tantorlabs(dot)com>
Cc: pgsql-hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Re: Assertion failure when importing expression statistics with nested JSON values
Date: 2026-11-03 07:59:42
Message-ID: 31636E9F-1AF8-4784-8DC2-39576415D33A@gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

> On Nov 3, 2026, at 03:13, Ilia Evdokimov <ilya(dot)evdokimov(at)tantorlabs(dot)com> wrote:
>
> Hi,
>
> On an assertion-enabled build, `pg_restore_extended_stats()` triggers an assertion failure in check_all_expr_argnames_valid() when an expression statistics object contains an unknown JSON key with an array value
>
> ```
> CREATE TABLE t (i INT);
> CREATE STATISTICS t_stats ON (i + 1) FROM t;
> SELECT pg_catalog.pg_restore_extended_stats(
> 'schemaname', 'public',
> 'relname', 't',
> 'statistics_schemaname', 'public',
> 'statistics_name', 't_stats',
> 'inherited', false,
> 'exprs', '[{"unknown": []}]'::jsonb
> );
>
> server closed the connection unexpectedly
> This probably means the server terminated abnormally
> before or while processing the request.
> The connection to the server was lost. Attempting reset: Failed.
> The connection to the server was lost. Attempting reset: Failed.
> ```
>
> Logs
> ```
> [506971] WARNING: argument "exprs" has type text, expected type jsonb
> TRAP: failed Assert("jitok == WJB_VALUE"), File: "extended_stats_funcs.c", Line: 950, PID: 506971
> postgres: postgres postgres [local] SELECT(ExceptionalCondition+0x6f) [0x5bf2098a799f]
> postgres: postgres postgres [local] SELECT(pg_restore_extended_stats+0x2180) [0x5bf2096d5390]
> postgres: postgres postgres [local] SELECT(+0x3a50d0) [0x5bf2095110d0]
> postgres: postgres postgres [local] SELECT(+0x3e581a) [0x5bf20955181a]
> postgres: postgres postgres [local] SELECT(standard_ExecutorRun+0x18a) [0x5bf2095155ea]
> postgres: postgres postgres [local] SELECT(+0x5ca6f8) [0x5bf2097366f8]
> postgres: postgres postgres [local] SELECT(PortalRun+0x24e) [0x5bf20973812e]
> postgres: postgres postgres [local] SELECT(+0x5c7837) [0x5bf209733837]
> postgres: postgres postgres [local] SELECT(PostgresMain+0x17d3) [0x5bf2097354a3]
> postgres: postgres postgres [local] SELECT(BackendMain+0x53) [0x5bf20972f143]
> postgres: postgres postgres [local] SELECT(postmaster_child_launch+0x103) [0x5bf209669bb3]
> postgres: postgres postgres [local] SELECT(+0x501924) [0x5bf20966d924]
> postgres: postgres postgres [local] SELECT(PostmasterMain+0xd6b) [0x5bf20966f45b]
> postgres: postgres postgres [local] SELECT(main+0x1d8) [0x5bf2092febb8]
> /usr/lib/x86_64-linux-gnu/libc.so.6(+0x2a601) [0x7adbd022a601]
> /usr/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x88) [0x7adbd022a718]
> postgres: postgres postgres [local] SELECT(_start+0x25) [0x5bf2092fef25]
> [506931] LOG: client backend (PID 506971) was terminated by signal 6: Aborted
> [506931] DETAIL: Failed process was running: SELECT pg_catalog.pg_restore_extended_stats(
> 'schemaname', 'public',
> 'relname', 'test_expr_stats',
> 'statistics_schemaname', 'public',
> 'statistics_name', 'test_expr_stats_s',
> 'inherited', false,
> 'exprs', '[{"unknown": []}]'::jsonb
> );
> ```
>
>
> The fix passes skipNested = true when reading a value. This function only validates the keys of the outer object, so it should consume each value as a single WJB_VALUE token rather than descend into nested containers.
>
> Does anyone see any issues with this fix, or suggest a different approach?
>
> --
> Best regards,
> Ilia Evdokimov,
> Tantor Labs LLC,
> https://tantorlabs.com/
> <v1-0001-Fix-iteration-over-nested-JSON-values-in-expressi.patch>

I doubt this bug is likely to be encountered in practice, but I agree that the server should not crash on invalid input anyway.

The fix looks correct to me.

Looking at import_pg_statistic(), we can see that the existing code already rejects nested values for recognized keys.
```
/*
* Loop through all keys that we need to look up. If any value found is
* neither a string nor a NULL, there is not much we can do, so just give
* on the entire tuple for this expression.
*/
for (int i = 0; i < NUM_ATTRIBUTE_STATS_ELEMS; i++)
{
const char *s = extexprargname[i];
int len = strlen(s);

if (getKeyJsonValueFromContainer(cont, s, len, &val[i]) == NULL)
continue;

switch (val[i].type)
{
case jbvString:
found[i] = true;
break;

case jbvNull:
break;

default:
ereport(WARNING,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not parse \"%s\": invalid element in expression %d", argname, exprnum),
errhint("Value of element \"%s\" must be a null or a string.", s));
goto pg_statistic_error;
}
}
```

By changing skipNested to true, JsonbIteratorNext() returns WJB_VALUE for a nested array or object instead of descending into it. This avoids the assertion failure and allows the iterator to continue with the next top-level key.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Zsolt Parragi 2026-11-03 08:01:08 Re: pgbench --unlogged-tables silently ignored for branches/tellers/history when --partitions is used
Previous Message Andrei Lepikhov 2026-11-03 07:54:20 Re: Propagate LIMIT bound through the outer side of left join