| From: | Rui Zhao <zhaorui126(at)gmail(dot)com> |
|---|---|
| To: | Akshay Joshi <akshay(dot)joshi(at)enterprisedb(dot)com> |
| Cc: | pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Subject: | Re: [PATCH] Add pg_get_table_ddl() to reconstruct CREATE TABLE statements |
| Date: | 2026-07-24 07:18:26 |
| Message-ID: | CAHWVJhGBAtvugUF5Na+fLz=pmeu6DzQrj42UjLz_OY2sKcAjMQ@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi Akshay,
Sorry for the slow turnaround -- the World Cup got the better of my evenings.
A) Round-trip against pg_dump. I re-ran it on v22 (master 8767a10cb8): for each
of the 563 top-level tables in the regression database I ran pg_get_table_ddl,
dropped and recreated that table from its output in a copy of the database, and
diffed it against pg_dump. 504 of 563 round-trip clean, and the rate has climbed
with each round -- 483/560 at v17, 494/562 at v19, 501/562 at v21,
504/563 now --
with installcheck passing throughout.
For the record, here is what the remaining 59 are:
- 31 are objects pg_get_table_ddl doesn't emit by design (triggers, grants,
policies, comments, owned sequences). The pg_dump comparison flags them, but
that is expected.
- 17 are artifacts of testing (I replay into a copy with the table dropped),
not defects: 14 rely on an owned sequence or dependent function
that goes with
the drop and isn't part of the table's own DDL; 3 pick up a "1" suffix on a
NOT NULL constraint whose name a sibling table still holds. The
emitted DDL is
correct.
- 10 are column reordering on partition or inheritance children -- the
reordered-column limitation you deferred, plus its
inheritance-child variant.
- 1 is real but cosmetic: a partition child's named EXCLUDE constraint isn't
preserved -- it replays fine, only the child's name is regenerated. Just the
parent constraint is emitted:
CREATE TABLE ep (a int4range, b int4range) PARTITION BY LIST (a);
CREATE TABLE ep1 (a int4range, b int4range,
CONSTRAINT ep1_excl EXCLUDE USING gist (a WITH
=, b WITH &&));
ALTER TABLE ep ADD CONSTRAINT ep_excl EXCLUDE USING gist (a WITH
=, b WITH &&);
ALTER TABLE ep ATTACH PARTITION ep1 FOR VALUES IN ('[0,1)');
SELECT d FROM pg_get_table_ddl('ep', owner => false) d;
-- CREATE TABLE public.ep (a int4range, b int4range) PARTITION
BY LIST (a);
-- CREATE TABLE public.ep1 PARTITION OF public.ep FOR VALUES IN
('[0,1)');
-- ALTER TABLE public.ep ADD CONSTRAINT ep_excl EXCLUDE USING
gist (a WITH =, b WITH &&);
ep1's own constraint (ep1_excl) is never emitted, so on replay the parent's
ADD CONSTRAINT recreates it auto-named ep1_a_b_excl instead of ep1_excl.
So the substantive round-trip gaps come down to the reordered columns (your
deferred limitation) and that one EXCLUDE naming case; none breaks replay. (1b,
the conislocal locality case from my v19 pass -- a hand-built one, not in the
corpus above -- is the same family and still open, as you'd noted.)
B) only_kinds/except_kinds filtering. The cross-kind dependency check is only
half there. When a statement leans on an object another kind emits -- a
constraint's backing index -- and you filter that kind out, REPLICA IDENTITY
USING INDEX catches it and errors cleanly ('requires kind "primary_key" to be
emitted'); CLUSTER ON and a self-referential FK don't, and dangle:
CREATE TABLE b (x int);
ALTER TABLE b ADD CONSTRAINT b_pk PRIMARY KEY (x);
ALTER TABLE b CLUSTER ON b_pk;
SELECT d FROM pg_get_table_ddl('b', owner => false,
except_kinds => ARRAY['primary_key']) d;
-- CREATE TABLE public.b (x integer NOT NULL);
-- ALTER TABLE public.b CLUSTER ON b_pk;
-- replay: ERROR: index "b_pk" for table "b" does not exist
CREATE TABLE t (id int PRIMARY KEY, parent_id int REFERENCES t(id));
SELECT d FROM pg_get_table_ddl('t', owner => false,
except_kinds => ARRAY['primary_key']) d;
-- CREATE TABLE public.t (id integer NOT NULL, parent_id integer);
-- ALTER TABLE public.t ADD CONSTRAINT t_parent_id_fkey
-- FOREIGN KEY (parent_id) REFERENCES public.t(id);
-- replay: ERROR: there is no unique constraint matching given keys for
-- referenced table "t"
except_kinds => ARRAY['unique'] does the same. Both want the guard REPLICA
IDENTITY already has -- the regression test even covers it for all three index
kinds; CLUSTER ON and the FK case just aren't wired to it. A plain-index
CLUSTER ON and a cross-table FK are fine as they are -- the first drops with the
index kind, the second gets its key from the referenced table's own output.
C) One small code note. emit_partition_children() recurses into
pg_get_table_ddl_internal() once per partition-tree level:
children = find_inheritance_children(ctx->relid, AccessShareLock);
foreach(lc, children)
{
...
CHECK_FOR_INTERRUPTS();
...
childstmts = pg_get_table_ddl_internal(&childctx);
For user-triggerable recursion like this the convention is to put a
check_stack_depth() next to that CHECK_FOR_INTERRUPTS(). It does fail cleanly
today -- I threw a 12000-level partition tree at it and got the usual "stack
depth limit exceeded" -- but only because some callee happens to check; the
recursion itself never does.
Regards,
Rui
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Amit Kapila | 2026-07-24 07:20:08 | Re: Fix "unexpected logical decoding status change" error; from concurrent logical decoding activation |
| Previous Message | Fujii Masao | 2026-07-24 07:10:35 | Re: DOCS: Describe some missing parameters on CREATE/ALTER PUBLICATION pages |