EXPLAIN (ANALYZE, FORMAT JSON) emits invalid output for MERGE on a partitioned table

From: Hironobu SUZUKI <hironobu(at)interdb(dot)jp>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Subject: EXPLAIN (ANALYZE, FORMAT JSON) emits invalid output for MERGE on a partitioned table
Date: 2026-11-06 10:39:16
Message-ID: 7992298c-6f88-4784-bef7-94a860e3a706@interdb.jp
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

Hi,

While testing, I noticed that EXPLAIN (ANALYZE, FORMAT JSON) of a MERGE
whose target is a partitioned table produces syntactically invalid JSON.
The MERGE tuple counters ("Tuples Inserted", etc.) are emitted inside
the "Target Tables" array, so a bare "key": value pair appears inside a
JSON array.

Reproducer:
CREATE TABLE t (id int) PARTITION BY RANGE (id);
CREATE TABLE t1 PARTITION OF t DEFAULT;

EXPLAIN (ANALYZE, FORMAT JSON)
MERGE INTO t USING (VALUES (1)) s(id) ON t.id = s.id
WHEN NOT MATCHED THEN INSERT VALUES (s.id);

Actual output (excerpt):

"Target Tables": [
{
"Relation Name": "t1",
"Alias": "t_1"
},
"Tuples Inserted": 1,
"Tuples Updated": 0,
"Tuples Deleted": 0,
"Tuples Skipped": 0
],

This is rejected by JSON parsers. With a non-partitioned target, "Target
Tables" is not emitted (labeltargets is false), so the counters appear
as ordinary properties of the Plan object and the output is valid.

I reproduced this on master (commit edbe7ffdc8), 19rc1, 18.6, 17.11,
16.16 and 15.19; the same code is present in all branches that have MERGE.
(Text format is unaffected.)

Cause:

In show_modifytable_info(), the "Target Tables" group is opened before
the loop over result relations but is only closed at the very end of the
function, after the ON CONFLICT and MERGE properties have been emitted.
Those properties belong to the ModifyTable node itself, not to the list
of target tables.

Patch:

The attached patch (against master) closes the "Target Tables" group
right after the loop over the target relations. With it, the output for
the same query becomes:

"Target Tables": [
{
"Relation Name": "t1",
"Alias": "t_1"
}
],
"Tuples Inserted": 1,
"Tuples Updated": 0,
"Tuples Deleted": 0,
"Tuples Skipped": 0,

I tested the patch on master, 19rc1, 18.6, 17.11, 16.16 and 15.19. It
applies to all of them with git apply without modification, the JSON
output of the reproducer is valid with it, and make check passes on each.

Since the same code is present in all of these branches, a back-patch
seems appropriate.

Regards,
Hironobu Suzuki

Attachment Content-Type Size
v1-0001-Fix-EXPLAIN-output-of-MERGE-on-partitioned-tables.patch text/plain 1.9 KB

Browse pgsql-bugs by date

  From Date Subject
Next Message Fujii Masao 2026-11-06 14:41:26 Re: first_value()/nth_value() with IGNORE NULLS loop forever (EXCLUDE GROUP/TIES, frame past partition end, no ORDER BY)
Previous Message Ayush Tiwari 2026-11-06 09:56:46 Re: BUG #19603: Vuln47: distance_taxicab and distance_chebyshev silently return 0 instead of NaN when a cube coordin