Summary
For a hash partition with REMAINDER 0, the deparser omits the whole FOR VALUES WITH (...) clause, producing invalid SQL. REMAINDER 1, 2, … are fine.
Every hash-partitioned table has exactly one remainder-0 partition, so this affects every hash partitioning scheme.
Versions
pgsql-deparser@18.3.6
plpgsql-parser@18.5.8
libpg-query@18.1.4
- Node 24
Reproduction
import { parse } from 'plpgsql-parser'
import { deparse } from 'pgsql-deparser'
import { loadModule } from 'libpg-query'
await loadModule()
for (const r of [0, 1, 2]) {
const sql = `ALTER TABLE ONLY o ATTACH PARTITION o_p FOR VALUES WITH (MODULUS 4, REMAINDER ${r});`
console.log(await deparse((await parse(sql)).sql))
}
Expected
ALTER TABLE ONLY o ATTACH PARTITION o_p FOR VALUES WITH (MODULUS 4, REMAINDER 0);
Actual
REMAINDER 0 -> ALTER TABLE ONLY o ATTACH PARTITION o_p; <-- invalid
REMAINDER 1 -> ALTER TABLE ONLY o ATTACH PARTITION o_p FOR VALUES WITH (modulus 4, remainder 1);
REMAINDER 2 -> ALTER TABLE ONLY o ATTACH PARTITION o_p FOR VALUES WITH (modulus 4, remainder 2);
Cause
The AST loses the field, because protobuf JSON encoding omits zero-valued scalars:
REMAINDER 0 -> {"strategy":"h","modulus":4,"location":52}
REMAINDER 1 -> {"strategy":"h","modulus":4,"remainder":1,"location":52}
So remainder being absent is not distinguishable from remainder: 0 at the AST level — that part is arguably inherent to the encoding.
The actionable part is the deparser's response to it. Given strategy: "h", a missing remainder should be treated as 0 and rendered, rather than causing the entire FOR VALUES clause to be dropped. A hash bound is never valid without one, so the current behaviour cannot be correct in any case.
Aside
The clause is emitted in lower case (modulus / remainder). That is accepted by PostgreSQL, so it is cosmetic, but it differs from pg_dump's output if anyone is diffing rendered SQL as text.
Summary
For a hash partition with
REMAINDER 0, the deparser omits the wholeFOR VALUES WITH (...)clause, producing invalid SQL.REMAINDER 1,2, … are fine.Every hash-partitioned table has exactly one remainder-0 partition, so this affects every hash partitioning scheme.
Versions
pgsql-deparser@18.3.6plpgsql-parser@18.5.8libpg-query@18.1.4Reproduction
Expected
Actual
Cause
The AST loses the field, because protobuf JSON encoding omits zero-valued scalars:
So
remainderbeing absent is not distinguishable fromremainder: 0at the AST level — that part is arguably inherent to the encoding.The actionable part is the deparser's response to it. Given
strategy: "h", a missingremaindershould be treated as0and rendered, rather than causing the entireFOR VALUESclause to be dropped. A hash bound is never valid without one, so the current behaviour cannot be correct in any case.Aside
The clause is emitted in lower case (
modulus/remainder). That is accepted by PostgreSQL, so it is cosmetic, but it differs frompg_dump's output if anyone is diffing rendered SQL as text.