Skip to content

deparser: hash partition with REMAINDER 0 loses its entire FOR VALUES clause #350

Description

@jasdeepkhalsa

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions